OpenFPM  5.2.0
Project that contain the implementation of distributed structures
particle_cp.hpp
Go to the documentation of this file.
1 //
2 // Created by lschulze on 2021-10-12
3 //
27 #include <math.h>
28 #include "Vector/vector_dist.hpp"
29 #include "regression/regression.hpp"
30 
33 {
34  size_t max_iter = 1000; // params for the Newton algorithm for the solution of the constrained
35  double tolerance = 1e-11; // optimization problem, and for the projection of the sample points on the interface
36 
37  double H; // interparticle spacing
38  double r_cutoff_factor; // radius of neighbors to consider during interpolation, as factor of H
39  double sampling_radius;
40 
41  int compute_normals = 0;
42  int compute_curvatures = 0;
43 
44  int write_sdf = 1;
45  int write_cp = 0;
46 
47  unsigned int minter_poly_degree = 4;
48  float minter_lp_degree = 1.0;
49 
50  int verbose = 0;
51  int min_num_particles = 0; // if 0, cutoff radius is used, else the int is the minimum number of particles for the regression.
52  // If min_num_particles is used, the cutoff radius is used for the cell list. It is sensible to use
53  // a smaller rcut for the celllist, to promote symmetric supports (otherwise it is possible that the
54  // particle is at the corner of a cell and hence only has neighbors in certain directions)
55  float r_cutoff_factor_min_num_particles; // this is the rcut for the celllist
56  int only_narrowband = 1; // only redistance particles with phi < sampling_radius, or all particles if only_narrowband = 0
57  int project_particles = 0; // change the actual position of the particles by projection onto their respective closest points.
58  // to perform this, verbose needs to be 0 as it interferes with the surface flag which is queried
59  // for the projection.
60 };
61 
62 template <typename particles_in_type, size_t phi_field, size_t closest_point_field, size_t normal_field, size_t curvature_field, unsigned int num_minter_coeffs>
64 {
65 public:
66  particle_cp_redistancing(particles_in_type & vd, Redist_options &redistOptions) : redistOptions(redistOptions),
67  vd_in(vd),
68  vd_s(vd.getDecomposition(), 0),
69  r_cutoff2(redistOptions.r_cutoff_factor*redistOptions.r_cutoff_factor*redistOptions.H*redistOptions.H),
70  minterModelpcp(RegressionModel<dim, vd_s_sdf>(
71  redistOptions.minter_poly_degree, redistOptions.minter_lp_degree))
72  {
73  // Constructor
74  }
75 
76  void run_redistancing()
77  {
78  if (redistOptions.verbose)
79  {
80  std::cout<<"Verbose mode. Make sure the vd.getProp<4>(a) is an integer that pcp can write surface flags onto."<<std::endl;
81  }
82 
83  detect_surface_particles();
84 
85  interpolate_sdf_field();
86 
87  find_closest_point(vd_in);
88  }
89 
90  void redistance_separate_particle_set(particles_in_type & vd_generic)
91  {
92  find_closest_point(vd_generic);
93  }
94 
95  // compute and return sample particles
96  particles_in_type initialize_surface_discretization()
97  {
98  detect_surface_particles();
99 
100  interpolate_sdf_field();
101 
102  particles_in_type sampleParticles = format_vd_s();
103 
104  return(sampleParticles);
105  }
106 
107  // interpolate field to given particle positions
108  template <size_t prp_id, size_t prp_id_to> void regress_field(particles_in_type & vd_generic)
109  {
110  regress_field_to_particles<prp_id, prp_id_to>(vd_generic);
111  }
112 
113 private:
114  static constexpr size_t num_neibs = 0;
115  static constexpr size_t vd_s_close_part = 1;
116  static constexpr size_t vd_s_sdf = 2;
117  static constexpr size_t vd_s_sample = 3;
118  static constexpr size_t minter_coeff = 4;
119  // static constexpr size_t vd_s_velocity_field = 5;
120  static constexpr size_t vd_in_sdf = phi_field; // this is really required in the vd_in vector, so users need to know about it.
121  static constexpr size_t vd_in_close_part = 4; // this is not needed by the method, but is used for debugging purposes, as it shows all particles for which
122  // interpolation and sampling is performed. Also, it flags particles that are supposed to be moved (or projected)
123  // onto the surface.
124  static constexpr size_t vd_in_normal = normal_field;
125  static constexpr size_t vd_in_curvature = curvature_field;
126  static constexpr size_t vd_in_cp = closest_point_field;
127 
128  Redist_options redistOptions;
129  particles_in_type &vd_in;
130  static constexpr unsigned int dim = particles_in_type::dims;
131  static constexpr unsigned int n_c = num_minter_coeffs;
132 
133  int dim_r = dim;
134  int n_c_r = n_c;
135 
137  double r_cutoff2;
138  RegressionModel<dim, vd_s_sdf> minterModelpcp;
139 
140  int return_sign(double phi)
141  {
142  if (phi > 0) return 1;
143  if (phi < 0) return -1;
144  return 1;
145  }
146 
147  // this function lays the groundwork for the interpolation step. It classifies particles according to two possible classes:
148  // (a) close particles: These particles are close to the surface and will be the central particle in the subsequent interpolation step.
149  // After the interpolation step, they will also be projected on the surface. The resulting position on the surface will be referred to as a sample point,
150  // which will provide the initial guesses for the final Newton optimization, which finds the exact closest-point towards any given query point.
151  // Further, the number of neighbors in the support of close particles are stored, to efficiently initialize the Vandermonde matrix in the interpolation.
152  // (b) surface particles: These particles have particles in their support radius, which are close particles (a). In order to compute the interpolation
153  // polynomials for (a), these particles need to be stored. An alternative would be to find the relevant particles in the original particle vector.
154 
155  void detect_surface_particles()
156  {
157  vd_in.template ghost_get<vd_in_sdf>();
158 
159  auto NN = vd_in.getCellList(sqrt(r_cutoff2) + redistOptions.H);
160  auto part = vd_in.getDomainIterator();
161 
162  while (part.isNext())
163  {
164  vect_dist_key_dx akey = part.get();
165  // depending on the application this can spare computational effort
166  if ((redistOptions.only_narrowband) && (std::abs(vd_in.template getProp<vd_in_sdf>(akey)) > redistOptions.sampling_radius))
167  {
168  ++part;
169  continue;
170  }
171  int surfaceflag = 0;
172  int sgn_a = return_sign(vd_in.template getProp<vd_in_sdf>(akey));
173  Point<dim,double> xa = vd_in.getPos(akey);
174  int num_neibs_a = 0;
175  double min_sdf = abs(vd_in.template getProp<vd_in_sdf>(akey));
176  vect_dist_key_dx min_sdf_key = akey;
177  if (redistOptions.verbose) vd_in.template getProp<vd_in_close_part>(akey) = 0;
178  int isclose = 0;
179 
180  auto Np = NN.getNNIteratorBox(NN.getCell(xa));
181  while (Np.isNext())
182  {
183  vect_dist_key_dx bkey = Np.get();
184  int sgn_b = return_sign(vd_in.template getProp<vd_in_sdf>(bkey));
185  Point<dim, double> xb = vd_in.getPos(bkey);
186  Point<dim, double> dr = xa - xb;
187  double r2 = norm2(dr);
188 
189  // check if the particle will provide a polynomial interpolation and a sample point on the interface
190  if ((sqrt(r2) < (1.5*redistOptions.H)) and (sgn_a != sgn_b)) isclose = 1;
191 
192  // count how many particles are in the neighborhood and will be part of the interpolation
193  if (r2 < r_cutoff2) ++num_neibs_a;
194 
195  // decide whether this particle will be required for any interpolation. This is the case if it has a different sign in its slightly extended neighborhood
196  // its up for debate if this is stable or not. Possibly, this could be avoided by simply using vd_in in the interpolation step.
197  if ((sqrt(r2) < ((redistOptions.r_cutoff_factor + 1.5)*redistOptions.H)) && (sgn_a != sgn_b)) surfaceflag = 1;
198  ++Np;
199 
200  }
201 
202  if (surfaceflag) // these particles will play a role in the subsequent interpolation: Either they carry interpolation polynomials,
203  { // or they will be taken into account in interpolation
204  vd_s.add();
205  for(int k = 0; k < dim; k++) vd_s.getLastPos()[k] = vd_in.getPos(akey)[k];
206  vd_s.template getLastProp<vd_s_sdf>() = vd_in.template getProp<vd_in_sdf>(akey);
207  vd_s.template getLastProp<num_neibs>() = num_neibs_a;
208 
209  if (isclose) // these guys will carry an interpolation polynomial and a resulting sample point
210  {
211  vd_s.template getLastProp<vd_s_close_part>() = 1;
212  if (redistOptions.verbose) vd_in.template getProp<vd_in_close_part>(akey) = 1;
213  }
214  else // these particles will not carry an interpolation polynomial
215  {
216  vd_s.template getLastProp<vd_s_close_part>() = 0;
217  if (redistOptions.verbose) vd_in.template getProp<vd_in_close_part>(akey) = 0;
218  }
219  }
220  ++part;
221  }
222  }
223 
224  void interpolate_sdf_field()
225  {
226  int message_insufficient_support = 0;
227  int message_projection_fail = 0;
228 
229  vd_s.template ghost_get<vd_s_sdf>();
230  double r_cutoff_celllist = sqrt(r_cutoff2);
231  if (redistOptions.min_num_particles != 0) r_cutoff_celllist = redistOptions.r_cutoff_factor_min_num_particles*redistOptions.H;
232  auto NN_s = vd_s.getCellList(r_cutoff_celllist);
233  auto part = vd_s.getDomainIterator();
234 
235  // iterate over particles that will get an interpolation polynomial and generate a sample point
236  while (part.isNext())
237  {
238  vect_dist_key_dx a = part.get();
239 
240  // only the close particles (a) will get the full treatment (interpolation + projection)
241  if (vd_s.template getProp<vd_s_close_part>(a) != 1)
242  {
243  ++part;
244  continue;
245  }
246 
247  const int num_neibs_a = vd_s.template getProp<num_neibs>(a);
248  Point<dim, double> xa = vd_s.getPos(a);
249  int neib = 0;
250  int k_project = 0;
251 
252  if(redistOptions.min_num_particles == 0)
253  {
254  auto regSupport = RegressionSupport<decltype(vd_s), decltype(NN_s)>(vd_s, part, sqrt(r_cutoff2), RADIUS, NN_s);
255  if (regSupport.getNumParticles() < n_c) message_insufficient_support = 1;
256  minterModelpcp.computeCoeffs(vd_s, regSupport);
257  }
258  else
259  {
260  auto regSupport = RegressionSupport<decltype(vd_s), decltype(NN_s)>(vd_s, part, n_c + 3, AT_LEAST_N_PARTICLES, NN_s);
261  if (regSupport.getNumParticles() < n_c) message_insufficient_support = 1;
262  minterModelpcp.computeCoeffs(vd_s, regSupport);
263  }
264 
265  auto& minterModel = minterModelpcp.model;
266  for(int k = 0; k < n_c; k++) vd_s.template getProp<minter_coeff>(a)[k] = minterModel->getCoeffs()[k];
267 
268  double grad_p_minter_mag2;
269 
270  EMatrix<double, Eigen::Dynamic, 1> grad_p_minter(dim_r, 1);
271  EMatrix<double, Eigen::Dynamic, 1> x_minter(dim_r, 1);
272  for(int k = 0; k < dim_r; k++) x_minter[k] = xa[k];
273 
274  double p_minter = get_p_minter(x_minter, minterModel);
275  k_project = 0;
276  while ((abs(p_minter) > redistOptions.tolerance) && (k_project < redistOptions.max_iter))
277  {
278  grad_p_minter = get_grad_p_minter(x_minter, minterModel);
279  grad_p_minter_mag2 = grad_p_minter.dot(grad_p_minter);
280  x_minter = x_minter - p_minter*grad_p_minter/grad_p_minter_mag2;
281  p_minter = get_p_minter(x_minter, minterModel);
282  //incr = sqrt(p*p*dpdx*dpdx/(grad_p_mag2*grad_p_mag2) + p*p*dpdy*dpdy/(grad_p_mag2*grad_p_mag2));
283  ++k_project;
284  }
285  // store the resulting sample point on the surface at the central particle.
286  for(int k = 0; k < dim; k++) vd_s.template getProp<vd_s_sample>(a)[k] = x_minter[k];
287  if (k_project == redistOptions.max_iter)
288  {
289  if (redistOptions.verbose) std::cout<<"didnt work for "<<a.getKey()<<std::endl;
290  message_projection_fail = 1;
291  }
292 
293  ++part;
294  }
295 
296  if (message_insufficient_support) std::cout<<"Warning: less number of neighbours than required for interpolation"
297  <<" for some particles. Consider using at least N particles function."<<std::endl;
298  if (message_projection_fail) std::cout<<"Warning: Newton-style projections towards the interface do not satisfy"
299  <<" given tolerance for some particles"<<std::endl;
300 
301  }
302 
303  // This function now finds the exact closest point on the surface to any given particle.
304  // For this, it iterates through all particles in the input vector (since all need to be redistanced),
305  // and finds the closest sample point on the surface. Then, it uses this sample point as an initial guess
306  // for a subsequent optimization problem, which finds the exact closest point on the surface.
307  // The optimization problem is formulated as follows: Find the point in space with the minimal distance to
308  // the given query particle, with the constraint that it needs to lie on the surface. It is realized with a
309  // Lagrange multiplier that ensures that the solution lies on the surface, i.e. p(x)=0.
310  // The polynomial that is used for checking if the constraint is fulfilled is the interpolation polynomial
311  // carried by the sample point.
312 
313  void find_closest_point(particles_in_type & vd_generic)
314  {
315  // iterate over all particles, i.e. do closest point optimisation for all particles, and initialize
316  // all relevant variables.
317 
318  vd_s.template ghost_get<vd_s_close_part,vd_s_sample,minter_coeff>();
319  auto NN_s = vd_s.getCellList(redistOptions.sampling_radius);
320  auto part = vd_generic.getDomainIterator();
321 
322  int message_step_limitation = 0;
323  int message_convergence_problem = 0;
324 
325  // do iteration over all query particles
326  while (part.isNext())
327  {
328  vect_dist_key_dx a = part.get();
329 
330  if ((redistOptions.only_narrowband) && (std::abs(vd_generic.template getProp<vd_in_sdf>(a)) > redistOptions.sampling_radius))
331  {
332  ++part;
333  continue;
334  }
335 
336  // initialise all variables specific to the query particle
337  EMatrix<double, Eigen::Dynamic, 1> xa(dim_r, 1);
338  for(int k = 0; k < dim; k++) xa[k] = vd_generic.getPos(a)[k];
339 
340  double val;
341  // spatial variable that is optimized
342  EMatrix<double, Eigen::Dynamic, 1> x(dim_r, 1);
343  // Increment variable containing the increment of spatial variables + 1 Lagrange multiplier
344  EMatrix<double, Eigen::Dynamic, 1> dx(dim_r + 1, 1);
345  for(int k = 0; k < (dim + 1); k++) dx[k] = 1.0;
346 
347  // Lagrange multiplier lambda
348  double lambda = 0.0;
349  // values regarding the gradient of the Lagrangian and the Hessian of the Lagrangian, which contain
350  // derivatives of the constraint function also.
351  double p = 0;
352  EMatrix<double, Eigen::Dynamic, 1> grad_p(dim_r, 1);
353  for(int k = 0; k < dim; k++) grad_p[k] = 0.0;
354 
355  EMatrix<double, Eigen::Dynamic, Eigen::Dynamic> H_p(dim_r, dim_r);
356  for(int k = 0; k < dim; k++)
357  {
358  for(int l = 0; l < dim; l++) H_p(k, l) = 0.0;
359  }
360 
361  EMatrix<double, Eigen::Dynamic, 1> c(n_c_r, 1);
362  for (int k = 0; k < n_c_r; k++) c[k] = 0.0;
363 
364  // f(x, lambda) is the Lagrangian, initialize its gradient and Hessian
365  EMatrix<double, Eigen::Dynamic, 1> nabla_f(dim_r + 1, 1);
366  EMatrix<double, Eigen::Dynamic, Eigen::Dynamic> H_f(dim_r + 1, dim_r + 1);
367  for(int k = 0; k < (dim + 1); k++)
368  {
369  nabla_f[k] = 0.0;
370  for(int l = 0; l < (dim + 1); l++) H_f(k, l) = 0.0;
371  }
372 
373  // Initialise variables for searching the closest sample point. Note that this is not a very elegant
374  // solution to initialise another x_a vector, but it is done because of the two different types EMatrix and
375  // point vector, and can probably be made nicer.
376  Point<dim, double> xaa = vd_generic.getPos(a);
377 
378  // Now we will iterate over the sample points, which means iterating over vd_s.
379  auto Np = NN_s.getNNIteratorBox(NN_s.getCell(vd_generic.getPos(a)));
380 
381  vect_dist_key_dx b_min = get_closest_neighbor<decltype(NN_s)>(xaa, NN_s);
382 
383  // set x0 to the sample point which was closest to the query particle
384  for(int k = 0; k < dim; k++) x[k] = vd_s.template getProp<vd_s_sample>(b_min)[k];
385 
386  // these two variables are mainly required for optional subroutines in the optimization procedure and for
387  // warnings if the solution strays far from the neighborhood.
388  EMatrix<double, Eigen::Dynamic, 1> x00(dim_r,1);
389  for(int k = 0; k < dim; k++) x00[k] = x[k];
390 
391  EMatrix<double, Eigen::Dynamic, 1> x00x(dim_r,1);
392  for(int k = 0; k < dim; k++) x00x[k] = 0.0;
393 
394  auto& model = minterModelpcp.model;
395  EVectorXd temp(n_c_r,1);
396  for(int k = 0; k < n_c_r; k++) temp[k] = vd_s.template getProp<minter_coeff>(b_min)[k];
397  model->setCoeffs(temp);
398 
399  if(redistOptions.verbose)
400  {
401  std::cout<<std::setprecision(16)<<"VERBOSE%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% for particle "<<a.getKey()<<std::endl;
402  std::cout<<"x_poly: "<<vd_s.getPos(b_min)[0]<<", "<<vd_s.getPos(b_min)[1]<<"\nxa: "<<xa[0]<<", "<<xa[1]<<"\nx_0: "<<x[0]<<", "<<x[1]<<"\nc: "<<c<<std::endl;
403 
404  std::cout<<"interpol_i(x_0) = "<<get_p_minter(x, model)<<std::endl;
405  }
406 
407  // variable containing updated distance at iteration k
408  EMatrix<double, Eigen::Dynamic, 1> xax = x - xa;
409  // iteration counter k
410  int k_newton = 0;
411 
412  // calculations needed specifically for k_newton == 0
413  p = get_p_minter(x, model);
414  grad_p = get_grad_p_minter(x, model);
415 
416  // this guess for the Lagrange multiplier is taken from the original paper by Saye and can be done since
417  // p is approximately zero at the sample point.
418  lambda = -xax.dot(grad_p)/grad_p.dot(grad_p);
419 
420  for(int k = 0; k < dim; k++) nabla_f[k] = xax[k] + lambda*grad_p[k];
421  nabla_f[dim] = p;
422 
423  nabla_f(Eigen::seq(0, dim - 1)) = xax + lambda*grad_p;
424  nabla_f[dim] = p;
425  // The exit criterion for the Newton optimization is on the magnitude of the gradient of the Lagrangian,
426  // which is zero at a solution.
427  double nabla_f_norm = nabla_f.norm();
428 
429  // Do Newton iterations.
430  while((nabla_f_norm > redistOptions.tolerance) && (k_newton<redistOptions.max_iter))
431  {
432  // gather required derivative values
433  H_p = get_H_p_minter(x, model);
434 
435  // Assemble Hessian matrix, grad f has been computed at the end of the last iteration.
436  H_f(Eigen::seq(0, dim_r - 1), Eigen::seq(0, dim_r - 1)) = lambda*H_p;
437  for(int k = 0; k < dim; k++) H_f(k, k) = H_f(k, k) + 1.0;
438  H_f(Eigen::seq(0, dim_r - 1), dim_r) = grad_p;
439  H_f(dim_r, Eigen::seq(0, dim_r - 1)) = grad_p.transpose();
440  H_f(dim_r, dim_r) = 0.0;
441 
442  // compute Newton increment
443  dx = - H_f.inverse()*nabla_f;
444 
445  // prevent Newton algorithm from leaving the support radius by scaling step size. It is invoked in case
446  // the increment exceeds 50% of the cutoff radius and simply scales the step length down to 10% until it
447  // does not exceed 50% of the cutoff radius.
448 
449  while((dx.dot(dx)) > 0.25*r_cutoff2)
450  {
451  message_step_limitation = 1;
452  dx = 0.1*dx;
453  }
454 
455  // apply increment and compute new iteration values
456  x = x + dx(Eigen::seq(0, dim - 1));
457  lambda = lambda + dx[dim];
458 
459  // prepare values for next iteration and update the exit criterion
460  xax = x - xa;
461  p = get_p_minter(x, model);
462  grad_p = get_grad_p_minter(x, model);
463 
464  nabla_f(Eigen::seq(0, dim - 1)) = xax + lambda*grad_p;
465  nabla_f[dim] = p;
466 
467  //norm_dx = dx.norm(); // alternative criterion: incremental tolerance
468  nabla_f_norm = nabla_f.norm();
469 
470  ++k_newton;
471 
472  if(redistOptions.verbose)
473  {
474  std::cout<<"dx: "<<dx[0]<<", "<<dx[1]<<std::endl;
475  std::cout<<"H_f:\n"<<H_f<<"\nH_f_inv:\n"<<H_f.inverse()<<std::endl;
476  std::cout<<"x:"<<x[0]<<", "<<x[1]<<"\nc:"<<std::endl;
477  std::cout<<c<<std::endl;
478  std::cout<<"dpdx: "<<grad_p<<std::endl;
479  std::cout<<"k = "<<k_newton<<std::endl;
480  std::cout<<"x_k = "<<x[0]<<", "<<x[1]<<std::endl;
481  std::cout<<x[0]<<", "<<x[1]<<", "<<nabla_f_norm<<std::endl;
482  }
483  }
484 
485  // Check if the Newton algorithm achieved the required accuracy within the allowed number of iterations.
486  // If not, throw a warning, but proceed as usual (even if the accuracy dictated by the user cannot be reached,
487  // the result can still be very good.
488  if (k_newton == redistOptions.max_iter) message_convergence_problem = 1;
489 
490  // And finally, compute the new sdf value as the distance of the query particle x_a and the result of the
491  // optimization scheme x. We conserve the initial sign of the sdf, since the particle moves with the phase
492  // and does not cross the interface.
493  if (redistOptions.write_sdf) vd_generic.template getProp<vd_in_sdf>(a) = return_sign(vd_generic.template getProp<vd_in_sdf>(a))*xax.norm();
494  if (redistOptions.write_cp) for(int k = 0; k <dim; k++) vd_generic.template getProp<vd_in_cp>(a)[k] = x[k];
495  // If particles contain a flag in position 4 and the redistOptions is set accordingly, project particles onto their closest point on
496  // the surface.
497  if ((!redistOptions.verbose) and (vd_generic.template getProp<vd_in_close_part>(a)) and redistOptions.project_particles)
498  {
499  for(int k = 0; k < dim; k++) vd_generic.getPos(a)[k] = vd_generic.template getProp<vd_in_cp>(a)[k];
500  }
501  // This is to avoid loss of mass conservation - in some simulations, a particle can get assigned a 0-sdf, so
502  // that it lies on the surface and cannot be distinguished from the one phase or the other. The reason for
503  // this probably lies in the numerical tolerance. As a quick fix, we introduce an error of the tolerance,
504  // but keep the sign.
505  if ((k_newton == 0) && (xax.norm() < redistOptions.tolerance))
506  {
507  vd_generic.template getProp<vd_in_sdf>(a) = return_sign(vd_generic.template getProp<vd_in_sdf>(a))*redistOptions.tolerance;
508  }
509 
510  // debug optimisation
511  if(redistOptions.verbose)
512  {
513  //std::cout<<"new sdf: "<<vd.getProp<sdf>(a)<<std::endl;
514  std::cout<<"x_final: "<<x[0]<<", "<<x[1]<<std::endl;
515  std::cout<<"p(x_final) :"<<get_p_minter(x, model)<<std::endl;
516  std::cout<<"nabla p(x_final)"<<get_grad_p_minter(x, model)<<std::endl;
517 
518  std::cout<<"lambda: "<<lambda<<std::endl;
519  }
520 
521  if (redistOptions.compute_normals)
522  {
523  EMatrix<double, Eigen::Dynamic, 1> normal = get_normal(grad_p, return_sign(vd_generic.template getProp<vd_in_sdf>(a)));
524  for(int k = 0; k<dim; k++) vd_generic.template getProp<vd_in_normal>(a)[k] = normal[k];
525 
526  EMatrix<double, Eigen::Dynamic, 1> grad_p_iso(dim_r, 1);
527  grad_p_iso = get_grad_p_minter(xa, model);
528  //vd_generic.template getProp<11>(a)[0] = grad_p_iso[0];
529  //vd_generic.template getProp<11>(a)[1] = grad_p_iso[1];
530  }
531 
532  if (redistOptions.compute_curvatures)
533  {
534  H_p = get_H_p_minter(x, model);
535  vd_generic.template getProp<vd_in_curvature>(a) = get_curvature(grad_p, H_p);
536  }
537  ++part;
538  }
539  if (redistOptions.verbose and message_step_limitation)
540  {
541  std::cout<<"Step size limitation invoked"<<std::endl;
542  }
543  if (message_convergence_problem)
544  {
545  std::cout<<"Warning: Newton algorithm has reached maximum number of iterations, does not converge for some particles"<<std::endl;
546  }
547 
548  }
549 
550  template<typename NNlist_type> vect_dist_key_dx get_closest_neighbor(Point<dim, double> & xa, NNlist_type & NN_s)
551  {
552  auto Np = NN_s.getNNIteratorBox(NN_s.getCell(xa));
553 
554  // distance is the the minimum distance to be beaten
555  double distance = 1000000.0;
556  // dist_calc is the variable for the distance that is computed per sample point
557  double dist_calc = 1000000000.0;
558  // b_min is the handle referring to the particle that carries the sample point with the minimum distance so far
559  vect_dist_key_dx b_min = Np.get();
560 
561  while(Np.isNext())
562  {
563  vect_dist_key_dx b = Np.get();
564 
565  if (!vd_s.template getProp<vd_s_close_part>(b))
566  {
567  ++Np;
568  continue;
569  }
570  Point<dim, double> xb = vd_s.template getProp<vd_s_sample>(b);
571  dist_calc = norm(xa - xb);
572  if (dist_calc < distance)
573  {
574  distance = dist_calc;
575  b_min = b;
576  }
577 
578  ++Np;
579  }
580  return(b_min);
581  }
582  // reformat vd_s, such that it only contains the sample particles on the surface with their respective properties.
583  // Note that currently only domain particles are formatted, and ghost particles stay as is. A deleteGhost() call erases them in the main.
584  particles_in_type format_vd_s()
585  {
586  //vd_s.template ghost_get<vd_s_close_part, vd_s_sample>();
587  auto part = vd_s.getDomainIterator();
588  particles_in_type sampleParticles(vd_in.getDecomposition(), 0);
589 
590  while(part.isNext())
591  {
592  vect_dist_key_dx a = part.get();
593  // decide here whether the reformatted sample particles should be originating from both sides or only one side
594  // if (vd_s.template getProp<vd_s_close_part>(a) != 1) keys.add(a.getKey());
595  if ((vd_s.template getProp<vd_s_sdf>(a) < 0) and (vd_s.template getProp<vd_s_close_part>(a)))
596  {
597  sampleParticles.add();
598  for(int k = 0; k < dim; k++) sampleParticles.getLastPos()[k] = vd_s.template getProp<vd_s_sample>(a)[k];
599  sampleParticles.template getLastProp<vd_in_sdf>() = 0.0;
600  for(int k = 0; k < dim; k++) sampleParticles.template getLastProp<vd_in_cp>()[k] = vd_s.template getProp<vd_s_sample>(a)[k];
601  sampleParticles.template getLastProp<vd_in_close_part>() = 1;
602 
603  auto& model = minterModelpcp.model;
604  EVectorXd temp(n_c,1);
605  for(int k = 0; k < n_c; k++) temp[k] = vd_s.template getProp<minter_coeff>(a)[k];
606  model->setCoeffs(temp);
607  EMatrix<double, Eigen::Dynamic, 1> grad_p(dim, 1);
608  EMatrix<double, Eigen::Dynamic, Eigen::Dynamic> H_p(dim, dim);
609  EMatrix<double, Eigen::Dynamic, 1> normal(dim, 1);
610  EMatrix<double, Eigen::Dynamic, 1> x(dim, 1);
611  for (int k = 0; k < dim; k++) x[k] = vd_s.template getProp<vd_s_sample>(a)[k];
612  grad_p = get_grad_p_minter(x, model);
613  H_p = get_H_p_minter(x, model);
614  normal = get_normal(grad_p, 1);
615 
616  for (int k = 0; k < dim; k++) sampleParticles.template getLastProp<vd_in_normal>()[k] = normal[k];
617  sampleParticles.template getLastProp<vd_in_curvature>() = get_curvature(grad_p, H_p);
618  }
619  ++part;
620  }
621 
622  return(sampleParticles);
623  }
624 
625  template <size_t prp_id, size_t prp_id_to> void regress_field_to_particles(particles_in_type & vd_generic)
626  {
627  int message_insufficient_support = 0;
628  double r_cutoff_celllist = sqrt(r_cutoff2);
629  auto NN = vd_in.getCellList(r_cutoff_celllist);
630  auto part = vd_generic.getDomainIterator();
631  auto genericMinterModel = RegressionModel<dim, prp_id>(redistOptions.minter_poly_degree, redistOptions.minter_lp_degree);
632 
633  while(part.isNext())
634  {
635  vect_dist_key_dx a = part.get();
636  Point<dim, double> xa = vd_generic.getPos(a);
637  auto Np = NN.getNNIterator(NN.getCell(xa));
639 
640  while(Np.isNext())
641  {
642  vect_dist_key_dx b = Np.get();
643  Point<dim, double> xb = vd_in.getPos(b);
644  double dist2 = norm2(xb - xa);
645 
646  if(dist2 < r_cutoff2) keys.add(b.getKey());
647 
648  ++Np;
649  }
650 
651  auto regSupport = RegressionSupport<particles_in_type, decltype(NN)>(keys, vd_in, NN);
652  if (regSupport.getNumParticles() < n_c) message_insufficient_support = 1;
653  double divergence = 0.0;
654  for(int k = 0; k < dim; k++)
655  {
656  genericMinterModel.computeCoeffs(vd_in, regSupport, k);
657  auto& minterModel = genericMinterModel.model;
658  EMatrix<double, Eigen::Dynamic, 1> x(dim, 1);
659  EMatrix<double, Eigen::Dynamic, 1> grad_p_minter(dim, 1);
660  for (int l = 0; l < dim; l++) x[l] = xa[l];
661  vd_generic.template getProp<prp_id_to>(a)[k] = get_p_minter(x, minterModel);
662  // experimental: compute divergence of bulk velocity field
663  grad_p_minter = get_grad_p_minter(x, minterModel);
664  divergence = divergence + grad_p_minter[k];
665  }
666  vd_generic.template getProp<5>(a) = divergence;
667  ++part;
668  }
669 
670  if (message_insufficient_support) std::cout<<"Warning: less number of neighbours than required for property regression"
671  <<" for some particles. Consider using at least N particles function."<<std::endl;
672 
673  }
674 
675  // minterface
676  template<typename PolyType>
677  inline double get_p_minter(EMatrix<double, Eigen::Dynamic, 1> xvector, PolyType model)
678  {
679  return(model->eval(xvector.transpose())(0));
680  }
681 
682 
683  template<typename PolyType>
684  inline EMatrix<double, Eigen::Dynamic, 1> get_grad_p_minter(EMatrix<double, Eigen::Dynamic, 1> xvector, PolyType model)
685  {
686  EMatrix<double, Eigen::Dynamic, 1> grad_p(dim_r, 1);
687  std::vector<int> derivOrder(dim_r, 0);
688  for(int k = 0; k < dim_r; k++){
689  std::fill(derivOrder.begin(), derivOrder.end(), 0);
690  derivOrder[k] = 1;
691  grad_p[k] = model->deriv_eval(xvector.transpose(), derivOrder)(0);
692  }
693  return(grad_p);
694  }
695 
696  template<typename PolyType>
697  inline EMatrix<double, Eigen::Dynamic, Eigen::Dynamic> get_H_p_minter(EMatrix<double, Eigen::Dynamic, 1> xvector, PolyType model)
698  {
699  EMatrix<double, Eigen::Dynamic, Eigen::Dynamic> H_p(dim_r, dim_r);
700  std::vector<int> derivOrder(dim_r, 0);
701 
702  for(int k = 0; k < dim_r; k++){
703  for(int l = 0; l < dim_r; l++)
704  {
705  std::fill(derivOrder.begin(), derivOrder.end(), 0);
706  derivOrder[k]++;
707  derivOrder[l]++;
708  H_p(k, l) = model->deriv_eval(xvector.transpose(), derivOrder)(0);
709  }
710  }
711  return(H_p);
712  }
713 
714  inline EMatrix<double, Eigen::Dynamic, 1> get_normal(EMatrix<double, Eigen::Dynamic, 1> grad_p, float direction)
715  {
716  EMatrix<double, Eigen::Dynamic, 1> normal(dim_r, 1);
717 
718  for(int k = 0; k<dim_r; k++) normal[k] = direction*grad_p(k)*1/grad_p.norm();
719 
720  return(normal);
721  }
722 
723  inline double get_curvature(EMatrix<double, Eigen::Dynamic, 1> grad_p, EMatrix<double, Eigen::Dynamic, Eigen::Dynamic> H_p)
724  {
725  double kappa = 0.0;
726  // divergence of normalized gradient field:
727  if (dim == 2)
728  {
729  kappa = (H_p(0,0)*grad_p(1)*grad_p(1) - 2*grad_p(1)*grad_p(0)*H_p(0,1) + H_p(1,1)*grad_p(0)*grad_p(0))/std::pow(sqrt(grad_p(0)*grad_p(0) + grad_p(1)*grad_p(1)),3);
730  }
731  else if (dim == 3)
732  { // Mean curvature is 0.5*fluid mechanical curvature (see https://link.springer.com/article/10.1007/s00466-021-02128-9)
733  kappa = ((H_p(1,1) + H_p(2,2))*std::pow(grad_p(0), 2) + (H_p(0,0) + H_p(2,2))*std::pow(grad_p(1), 2) + (H_p(0,0) + H_p(1,1))*std::pow(grad_p(2), 2)
734  - 2*grad_p(0)*grad_p(1)*H_p(0,1) - 2*grad_p(0)*grad_p(2)*H_p(0,2) - 2*grad_p(1)*grad_p(2)*H_p(1,2))*std::pow(std::pow(grad_p(0), 2) + std::pow(grad_p(1), 2) + std::pow(grad_p(2), 2), -1.5);
735  }
736  return(kappa);
737  }
738 
739 };
740 
741 
ParticleIt_Cells< dim, CellList< dim, T, Mem_fast<>, transform > > getDomainIterator(openfpm::vector< size_t > &dom_cells)
Get an iterator over particles following the cell structure.
Definition: CellList.hpp:958
This class implement the point shape in an N-dimensional space.
Definition: Point.hpp:28
Class for reinitializing a level-set function into a signed distance function using the Closest-Point...
Definition: particle_cp.hpp:64
Grid key for a distributed grid.
Distributed vector.
auto getPos(vect_dist_key_dx vec_key) -> decltype(vPos.template get< 0 >(vec_key.getKey()))
Get the position of an element.
vector_dist_iterator getDomainIterator() const
Get an iterator that traverse the particles in the domain.
void add()
Add local particle.
CellList_type getCellList(St r_cut, size_t opt=CL_NON_SYMMETRIC|CL_LINEAR_CELL_KEYS, bool no_se3=false, float ghostEnlargeFactor=1.013)
Construct a cell list starting from the stored particles.
auto getLastPos() -> decltype(vPos.template get< 0 >(0))
Get the position of the last element.
Structure to bundle options for redistancing.
Definition: particle_cp.hpp:33