OpenFPM  5.2.0
Project that contain the implementation of distributed structures
main2.cpp
1 //
2 // Created by Abhinav Singh @absingh on 26/04/2021
3 //
41 
43 // Include Vector Expression,Vector Expressions for Subset,DCPSE,Odeint header files
44 #include "Operators/Vector/vector_dist_operators.hpp"
45 #include "Vector/vector_dist_subset.hpp"
46 #include "DCPSE/DCPSE_op/DCPSE_op.hpp"
47 #include "OdeIntegrators/OdeIntegrators.hpp"
49 
50 
73 
74 constexpr int x = 0;
75 constexpr int y = 1;
76 
77 double dt=1e-2,tf=1.0,vf=1.0;
78 
79 void *PointerDistGlobal, *PointerDistSubset,*PointerDistSubset2;
80 
85 
115 template<typename DXX,typename DYY>
116 struct RHSFunctor
117 {
118  //Intializing the operators
119  DXX &Dxx;
120  DYY &Dyy;
121  //Constructor
122  RHSFunctor(DXX &Dxx,DYY &Dyy):Dxx(Dxx),Dyy(Dyy)
123  {}
124 
125  void operator()( const state_type_2d_ofp &X , state_type_2d_ofp &dxdt , const double t ) const
126  {
127  //Casting the pointers to OpenFPM vector distributions
128  dist_vector_type &Particles= *(dist_vector_type *) PointerDistGlobal;
129  dist_vector_subset_type &Particles_bulk= *(dist_vector_subset_type *) PointerDistSubset;
130 
131  //Aliasing the properties.
132  auto C = getV<0>(Particles);
133  auto C_bulk = getV<0>(Particles_bulk);
134  auto dC = getV<2>(Particles);
135  auto dC_bulk = getV<2>(Particles_bulk);
136 
137  //Since the state vector does not have enough information to compute derivates, we copy them back to the Particle Distribution.
138  //In this way we can compute all the things that are required to be computed in each stage of the ode integrator.
139  // Note that we are going to use bulk expressions as needed. Whenever we don't want to update the boundaries (Due to the boundary conditions).
140 
141  //These expressions only update the bulk values of C.
142  C_bulk[x]=X.data.get<0>();
143  C_bulk[y]=X.data.get<1>();
144  Particles.ghost_get<0>();
145 
146  // We do the RHS computations for the Laplacian (Updating bulk only).
147  dC_bulk[x] = 0.1*(Dxx(C[x])+Dyy(C[x]));
148  dC_bulk[y] = 0.1*(Dxx(C[y])+Dyy(C[y]));
149 
150  //We copy back to the dxdt state_type for Odeint
151  dxdt.data.get<0>()=dC[x];
152  dxdt.data.get<1>()=dC[y];
153  }
154 };
156 
157 
186 template<typename DXX, typename DYY, typename VerletList_type>
188 
189  DXX &Dxx;
190  DYY &Dyy;
191  VerletList_type &verletList;
192  int ctr;
193  double t_old;
194  double rCut;
195 
196  //Constructor
197  ObserverFunctor(DXX &Dxx, DYY &Dyy, VerletList_type& verletList, double rCut) : Dxx(Dxx), Dyy(Dyy), verletList(verletList), rCut(rCut) {
198  //a counter for counting the np. of steps
199  ctr = 0;
200  //Starting with t=0, we compute the step size take by t-t_old. So for the first observed step size is what we provide. Which will be 0-(-dt)=dt.
201  t_old = -dt;
202  }
203 
204  void operator()(state_type_2d_ofp &X, double t) {
205  //Casting the pointers to OpenFPM vector distributions
206  dist_vector_type &Particles = *(dist_vector_type *) PointerDistGlobal;
207  dist_vector_subset_type &Particles_bulk = *(dist_vector_subset_type *) PointerDistSubset;
208  dist_vector_subset_type &Particles_boundary = *(dist_vector_subset_type *) PointerDistSubset2;
209 
210  //Aliasing the position and properties.
211  auto Pos = getV<POS_PROP>(Particles);
212  auto Concentration = getV<0>(Particles);
213  auto Velocity = getV<1>(Particles);
214  auto Concentration_bulk = getV<0>(Particles_bulk);
215  auto Velocity_bulk = getV<1>(Particles_bulk);
216 
217  //We would like to move the particles after t=0. (Remember, odeint calls the observer before taking the step.)
218  if (t != 0) {
219  //copy back the state after the time step into data structure. This is required as we are going to move the particles and the distributed state can be resized correctly (by copying back after map). Also this expression preserves the boundary condition on concentration.
220  Concentration_bulk[x] = X.data.get<0>();
221  Concentration_bulk[y] = X.data.get<1>();
222  //computing the velocity and move the particles
223  Velocity_bulk[x] = -vf*Pos[y] * exp(-10.0 * (Pos[x] * Pos[x] + Pos[y] * Pos[y]));
224  Velocity_bulk[y] = vf*Pos[x] * exp(-10.0 * (Pos[x] * Pos[x] + Pos[y] * Pos[y]));
225  Pos = Pos + dt * Velocity;
226  //Map and ghost_get is required after moving particles.
227  Particles.map();
228  Particles.ghost_get<0>();
229  //Updating the subset and operators based on new positions
230  Particles_bulk.update();
231  Particles_boundary.update();
232  Particles.updateVerlet(verletList,rCut);
233  Dxx.update(Particles);
234  Dyy.update(Particles);
235 
236  //Since we did Map, we assign the Odeint state again.
237  X.data.get<0>() = Concentration[x];
238  X.data.get<1>() = Concentration[y];
239  //counting the step number
240 
241  }
242  ctr++;
243  std::cout<<"Taking a step at t="<<t<<" with dt="<<t-t_old<<std::endl;
244  t_old=t;
245  //Writing the data after every step
246  Particles.deleteGhost();
247  Particles.write_frame("PDE_sol", ctr);
248  Particles.ghost_get<0>();
249 
250  }
251 };
253 
265 int main(int argc, char *argv[])
266 {
267  // initialize library
268  openfpm_init(&argc, &argv);
269 
270  dt=std::atof(argv[1]);
271  tf=std::atof(argv[2]);
272  vf=std::atof(argv[3]);
274 
288  const size_t sz[2] = {41, 41};
289  Box<2, double> box({-1, -1}, {1.0, 1.0});
290  size_t bc[2] = {NON_PERIODIC, NON_PERIODIC};
291  double spacing[2];
292  spacing[0] = 2.0 / (sz[0] - 1);
293  spacing[1] = 2.0 / (sz[1] - 1);
294  double rCut = 3.1 * spacing[0];
295  Ghost<2, double> ghost(rCut);
296 
297  dist_vector_type Particles(0, box, bc, ghost);
298  Particles.setPropNames({"Concentration", "Velocity", "TempConcentration"});
299 
300  auto it = Particles.getGridIterator(sz);
301  while (it.isNext()) {
302  Particles.add();
303  auto key = it.get();
304  double x = -1.0 + key.get(0) * spacing[0];
305  Particles.getLastPos()[0] = x;
306  double y = -1.0 + key.get(1) * spacing[1];
307  Particles.getLastPos()[1] = y;
308  //We detect Boundaries. By labelling the subset to be 0 for bulk and 1 for the boundary. Subsets will be constructed later based on the label.
309  if (x != -1.0 && x != 1.0 && y != -1.0 && y != 1) {
310  Particles.getLastSubset(0);
311  } else {
312  Particles.getLastSubset(1);
313  }
314  // Here fill the Initial value of the concentration.
315  if (x == 0.0 && y > -0.5 && y < 0) {
316  Particles.template getLastProp<0>()[0] = 1.0;
317  Particles.template getLastProp<0>()[1] = 0.0;
318  } else if (x == 0.0 && y > 0 && y < 0.5) {
319  Particles.template getLastProp<0>()[0] = 0.0;
320  Particles.template getLastProp<0>()[1] = 1.0;
321  } else {
322  Particles.template getLastProp<0>()[0] = 0.0;
323  Particles.template getLastProp<0>()[1] = 0.0;
324  }
325  ++it;
326  }
327  Particles.map();
328  Particles.ghost_get<0>();
329 
330  //We write the particles to check if the initialization is correct.
331  Particles.write("Init");
333 
346  // Now we initialize the grid with a filled circle. Outside the circle, the value of Phi_0 will be -1, inside +1.
347  //Now we construct the subsets based on the subset number.
348  dist_vector_subset_type Particles_bulk(Particles, 0);
349  dist_vector_subset_type Particles_boundary(Particles, 1);
350 
351  //We cast the global pointers to Particles and Particles_bulk as expected by the RHS functor.
352  PointerDistGlobal = (void *) &Particles;
353  PointerDistSubset = (void *) &Particles_bulk;
354  PointerDistSubset2 = (void *) &Particles_boundary;
356 
368  //We create the DCPSE Based operators for discretization of the operators.
369  auto verletList = Particles.template getVerlet<VL_NON_SYMMETRIC|VL_SKIP_REF_PART>(rCut);
370 
371  Derivative_xx<decltype(verletList)> Dxx(Particles, verletList, 2, rCut);
372  Derivative_yy<decltype(verletList)> Dyy(Particles, verletList, 2, rCut);
373  //We create aliases for referring to property and and positions.
374  auto Pos = getV<POS_PROP>(Particles);
375  auto C = getV<0>(Particles);
376  auto V = getV<1>(Particles);
377  auto dC = getV<2>(Particles);
378 
379  //We create aliases for referring to the subset properties.
380  auto C_bulk = getV<0>(Particles_bulk);
381  auto dC_bulk = getV<2>(Particles_bulk);
383 
399  //Now we create a odeint stepper object (RK4). Since we are in 2d, we are going to use "state_type_2d_ofp". Which is a structure or state_type compatible with odeint. We further pass all the parameters including "boost::numeric::odeint::vector_space_algebra_ofp",which tell odeint to use openfpm algebra.
400  // The template parameters are: state_type_2d_ofp (state type of X), double (type of the value inside the state), state_type_2d_ofp (state type of DxDt), double (type of the time), boost::numeric::odeint::vector_space_algebra_ofp (our algebra)
401  boost::numeric::odeint::runge_kutta4<state_type_2d_ofp, double, state_type_2d_ofp, double, boost::numeric::odeint::vector_space_algebra_ofp> Odeint_rk4;
402 
403  //The method Odeint_rk4 from Odeint, requires system (a function which computes RHS of the PDE), an instance of the Compute RHS functor. We create the System with the correct types and parameteres for the operators as declared before.
404  RHSFunctor<Derivative_xx<decltype(verletList)>, Derivative_yy<decltype(verletList)>> System(Dxx, Dyy);
405 
406  //Since we are using Odeint to control the time steps, we also create a observer instance. Which also updates the position via an euler step for moving thr particles.
407  ObserverFunctor<Derivative_xx<decltype(verletList)>, Derivative_yy<decltype(verletList)>, decltype(verletList)> ObserveAndUpdate(Dxx, Dyy, verletList, rCut);
408 
409 
410  //Furhter, odeint needs data in a state type "state_type_2d_ofp", we create one and fill in the initial condition.
412  //Since we created a 2d state_type we initialize the two fields in the object data using the method get.
413  X.data.get<x>() = C[0];
414  X.data.get<y>() = C[1];
416 
438  std::vector<double> inter_times; // vector to store intermediate time steps taken by odeint.
439 
440  size_t steps = boost::numeric::odeint::integrate_const(Odeint_rk4, System, X, 0.0, tf, dt, ObserveAndUpdate);
441  /* From Odeint: (Please refer to Odeint on more details about these steppers.)
442  runge_kutta_dopri5 is maybe the best default stepper. It has step size control as well as dense-output functionality. Simple create a dense-output stepper by make_dense_output( 1.0e-6 , 1.0e-5 , runge_kutta_dopri5< state_type >() ).
443  runge_kutta4 is a good stepper for constant step sizes. It is widely used and very well known. If you need to create artificial time series this stepper should be the first choice.
444  'runge_kutta_fehlberg78' is similar to the 'runge_kutta4' with the advantage that it has higher precision. It can also be used with step size control.
445  adams_bashforth_moulton is very well suited for ODEs where the r.h.s. is expensive (in terms of computation time). It will calculate the system function only once during each step.*/
446  /*Below are listed examples of using different steppers and their usage.*/
447  //size_t steps = integrate_adaptive(controlled_stepper_type() , System , X , t , tf , dt, ObserveAndUpdate);
448  //size_t steps = boost::numeric::odeint::integrate_adaptive( boost::numeric::odeint::make_controlled( 1.0e-7 , 1.0e-7 ,boost::numeric::odeint::runge_kutta_cash_karp54< state_type_2d_ofp,double,state_type_2d_ofp,double,boost::numeric::odeint::vector_space_algebra_ofp>() ) , System , X , t , tf , dt, ObserveAndUpdate );
449  //size_t steps = boost::numeric::odeint::integrate_const( boost::numeric::odeint::make_dense_output(1.0e-7, 1.0e-7, boost::numeric::odeint::runge_kutta_dopri5< state_type_2d_ofp,double,state_type_2d_ofp,double,boost::numeric::odeint::vector_space_algebra_ofp >() ) , System , X , t , tf , dt, ObserveAndUpdate );
450  //size_t steps = boost::numeric::odeint::integrate_adaptive( boost::numeric::odeint::make_dense_output( 1.0e-8 , 1.0e-8 , boost::numeric::odeint::runge_kutta_dopri5< state_type_2d_ofp,double,state_type_2d_ofp,double,boost::numeric::odeint::vector_space_algebra_ofp >() ) , System , X , t , tf , dt, ObserveAndUpdate );
451  //size_t steps = boost::numeric::odeint::integrate_adaptive( boost::numeric::odeint::make_controlled( 1.0e-7 , 1.0e-7 , boost::numeric::odeint::runge_kutta_dopri5< state_type_2d_ofp,double,state_type_2d_ofp,double,boost::numeric::odeint::vector_space_algebra_ofp >() ) , System , X , t , tf , dt, ObserveAndUpdate );
452 
453 
454  std::cout << "No. of Time steps taken: " << steps << std::endl;
455  //Copying back the final solution and outputting the number of steps taken by Odeint.
456  C_bulk[x] = X.data.get<0>();
457  C_bulk[y] = X.data.get<1>();
458  //Deallocating the operators
459  Dxx.deallocate(Particles);
460  Dyy.deallocate(Particles);
461  openfpm_finalize(); // Finalize openFPM library
462  return 0;
463 } //main end
465 
This class represent an N-dimensional box.
Definition: Box.hpp:60
Definition: Ghost.hpp:40
This class implement the point shape in an N-dimensional space.
Definition: Point.hpp:28
void update()
Update the subset indexes.
void updateVerlet(VerletList< dim, St, opt, Mem_type, shift< dim, St > > &verletList, St r_cut)
for each particle get the verlet list
void ghost_get(size_t opt=WITH_POSITION)
It synchronize the properties and position of the ghost particles.
void map(size_t opt=NONE)
It move all the particles that does not belong to the local processor to the respective processor.
void deleteGhost()
Delete the particles on the ghost.
aggregate of properties, from a list of object if create a struct that follow the OPENFPM native stru...
Definition: aggregate.hpp:221
A 2d Odeint and Openfpm compatible structure.