ScaleCoupling.h
Go to the documentation of this file.
1 // This file is part of the MercuryDPM project (https://www.mercurydpm.org).
2 // Copyright (c), The MercuryDPM Developers Team. All rights reserved.
3 // License: BSD 3-Clause License; see the LICENSE file in the root directory.
4 
5 #ifndef SCALE_COUPLING_H
6 #define SCALE_COUPLING_H
7 
8 #include <utility>
9 
10 #include "Mercury3D.h"
11 #include "../Coupling/BaseCoupling.h"
12 #include "generic.h"
14 #include "Oomph/SolidProblem.h" //
15 #include "ScaleCoupledElement.h"
16 using namespace oomph;
17 
18 template<class M, class O>
19 class ScaleCoupling : public BaseCoupling<M,O> {
20 
22  typedef typename O::ELEMENT ELEMENT;
23 
27  std::function<double(double x,double y,double z)> couplingWeight_;
28 
30  struct CoupledElement {
31  // pointer to the oomph-lib element
33  // particles in this element
34  std::vector<BaseParticle*> coupledParticles;
35  };
36 
38  std::vector<CoupledElement> coupledElements_;
39 
41  struct CoupledParticle {
45  CoupledElement* coupledElement_pt = nullptr;
48 
51  if (coupledElement_pt != nullptr) {
52  remove(coupledElement_pt->coupledParticles.begin(), coupledElement_pt->coupledParticles.end(), particle);
53  coupledElement_pt = nullptr;
54  logger(VERBOSE, "Removed particle % from element %", particle->getIndex(), coupledElement_pt);
55  }
56  }
57 
59  void setCoupledElement(CoupledElement* coupledElementNew, BaseParticle* particle) {
60  if (coupledElement_pt != coupledElementNew) {
61  if (coupledElement_pt != nullptr) {
62  auto newEnd = remove(coupledElement_pt->coupledParticles.begin(), coupledElement_pt->coupledParticles.end(), particle);
63  coupledElement_pt->coupledParticles.erase(newEnd, coupledElement_pt->coupledParticles.end());
64  logger(VERBOSE, "Moved particle % from element % to %", particle->getIndex(), coupledElement_pt, coupledElementNew);
65  } else {
66  logger(VERBOSE,"Added particle % to element %", particle->getIndex(), coupledElementNew);
67  }
68  // add coupledElement to particle
69  coupledElement_pt = coupledElementNew;
70  // add particle to coupledElement
71  coupledElement_pt->coupledParticles.push_back(particle);
72  }
73  }
74  };
75 
77  std::vector<CoupledParticle> coupledParticles_;
78 
80  double penalty_ = constants::NaN;
81 
82 public:
83 
85  const std::vector<CoupledElement>& getCoupledElements() {
86  return coupledElements_;
87  }
88 
90  const std::vector<CoupledParticle>& getCoupledParticles() {
91  return coupledParticles_;
92  }
93 
95  void setPenalty(double penalty) {
96  penalty_ = penalty;
97  }
98 
100  void setCouplingWeight(const std::function<double(double x,double y,double z)>& couplingWeight) {
101  couplingWeight_ = couplingWeight;
102  }
103 
106  {
107  // compute nStep
108  unsigned nStep = O::getOomphTimeStep()/M::getTimeStep();
109  if (nStep==0) {
110  // if oomph has a smaller time step than Mercury
111  logger(INFO, "Set nStep %, change mercuryTimeStep from % to %",
112  nStep, M::getTimeStep(), O::getOomphTimeStep());
113  nStep = 1;
114  M::setTimeStep(O::getOomphTimeStep());
115  } else {
116  logger(INFO, "Set nStep %, change oomphTimeStep from % to %",
117  nStep, O::getOomphTimeStep(), nStep * M::getTimeStep());
118  O::setOomphTimeStep(nStep * M::getTimeStep());
119  }
120  // call solve routine
121  solveScaleCoupling(nStep);
122  }
123 
124 private:
125 
127  void solveScaleCoupling(unsigned nStep)
128  {
129  // Check main parameters are set
130  logger.assert_always(O::getOomphTimeStep()>0, "Oomph time step not initialised");
131  logger.assert_always(M::getTimeStep()>0, "Mercury time step not initialised");
132  logger.assert_always(std::isfinite(penalty_), "Penalty parameter not set");
133  logger.assert_always((bool)couplingWeight_, "Coupling weight is not set");
134 
135  // Initialise the coupled element vector
136  initialiseCoupledElements();
137 
138  // This is the main loop advancing over time
139  unsigned nDone = 0; //< last written file number
140  while (M::getTime() < M::getTimeMax())
141  {
142  O::actionsBeforeOomphTimeStep();
143  // solve the coupled problem for one time step
144  computeOneTimeStepScaleCoupling(nStep);
145  // write outputs of the oomphProb; this is slaved to the vtk output of Mercury, i.e. an oomph-lib output get written everytime a Mercury vtk file gets written
146  if (M::getParticlesWriteVTK() && M::getVtkWriter()->getFileCounter() > nDone)
147  {
148  O::writeToVTK();
149  nDone = M::getVtkWriter()->getFileCounter();
150  }
151  }
152 
153  // Write final output files
154  M::finaliseSolve();
155  }
156 
159  // loop through all el_pts, check if any nodes have a fractional coupling weight.
160  // If yes, add the el_pt to the coupledElements_
161  auto nelement = O::mesh_pt()->nelement();
162  for (int e=0; e < nelement; ++e) {
163  bool coupledElement = false;
164  auto* el_pt = dynamic_cast<ELEMENT*>(O::mesh_pt()->finite_element_pt(e));
165  auto nnode = el_pt->nnode();
166  Vector<double> nodalCouplingWeights(nnode);
167  for (int n=0; n < nnode; ++n) {
168  auto* n_pt = dynamic_cast<SolidNode*>(el_pt->node_pt(n));
169  nodalCouplingWeights[n] = couplingWeight_(n_pt->xi(0), n_pt->xi(1), n_pt->xi(2));
170  if (nodalCouplingWeights[n]>0) { // check for nodalCouplingWeights[n]<1 ?
171  coupledElement = true;
172  logger(VERBOSE,"Coupled element at % % %, weight %",n_pt->xi(0), n_pt->xi(1), n_pt->xi(2), nodalCouplingWeights[n]);
173  }
174  }
175  if (coupledElement) {
176  coupledElements_.push_back({el_pt});
177  el_pt->set_coupling_weight(nodalCouplingWeights);
178  }
179  }
180  logger(INFO,"% of % el_pts are coupled", coupledElements_.size(), nelement);
181  }
182 
184  void computeOneTimeStepScaleCoupling(unsigned nStep) {
185  updateCoupledElements();
186  computeCouplingForce();
187  this->solveOomph();
188  this->solveMercury(nStep);
189  };
190 
193  {
197  // loop over all coupled elements
198  for (auto& coupledElement : coupledElements_)
199  {
200  ELEMENT* el_pt = coupledElement.el_pt;
201  const unsigned nnode = el_pt->nnode();
202  const unsigned dim = el_pt->dim(); // /todo I need to use el_pt->ndim=3, not el_pt->ndim=0, why?
203  // Find min and max
204  for (int n=0; n<nnode; ++n) {
205  auto* n_pt = dynamic_cast<SolidNode *>(el_pt->node_pt(n));
206  for (int d = 0; d < dim; ++d) {
207  min[d] = std::min(min[d], n_pt->x(d));
208  max[d] = std::max(max[d], n_pt->x(d));
209  }
210  }
211  }
212  logger(VERBOSE,"Coupling zone: % < x < %, % < y < %, % < z < % ",min[0],max[0],min[1],max[1],min[2],max[2]);
213 
215  // resize the coupledParticles vector
216  coupledParticles_.resize(M::particleHandler.getSize());
217  // now set the coupledElement pointer for each coupledParticles, and the particles vector for each coupledElement
218  for (int p=0; p<coupledParticles_.size(); ++p) {
219  CoupledParticle& coupledParticle = coupledParticles_[p];
220  Vec3D pos = M::particleHandler.getObject(p)->getPosition();
221  // this is the pointer we need to set
222  if (pos.X>=min[0] && pos.X<=max[0] && pos.Y>=min[1] && pos.Y<=max[1] && pos.Z>=min[2] && pos.Z<=max[2]) {
223  logger(VERBOSE,"Particle % of % is in coupling zone",p, M::particleHandler.getSize());
224  // if near the coupling zone
225  Vector<double> x {pos.X, pos.Y, pos.Z};
226  Vector<double>& s = coupledParticle.s;
227  GeomObject* geom_obj_pt;
228  // if el_pt is already set and still valid, keep
229  if (coupledParticle.coupledElement_pt != nullptr) {
230  coupledParticle.coupledElement_pt->el_pt->locate_zeta(x, geom_obj_pt, s);
231  if (geom_obj_pt!=nullptr) {
232  logger(VERBOSE,"Particle % remains coupled with element %",p, coupledParticle.coupledElement_pt->el_pt);
233  continue;
234  }
235  }
236  // otherwise, search for the right element
237  for (auto& coupledElement : coupledElements_) {
238  coupledElement.el_pt->locate_zeta(x,geom_obj_pt,s);
239  if (geom_obj_pt!=nullptr) {
240  logger(VERBOSE,"Coupled particle % to element % at % % %",p, &coupledElement, s[0], s[1], s[2]);
241  //\todo If a containing element is found, we stop; note though, particles on the boundary could belong to multiple elements; also not clear to me whether force should be applied to global basis function or local
242  coupledParticle.setCoupledElement(&coupledElement, M::particleHandler.getObject(p));
243  break;
244  }
245  }
246  } else {
247  // if away from the coupling, set coupledElement to null
248  if (coupledParticle.coupledElement_pt) logger(VERBOSE, "Removed particle % from element %", p, coupledParticle.coupledElement_pt);
249  coupledParticle.removeCoupledElement(M::particleHandler.getObject(p));
250  }
251  }
252  }
253 
256  {
257  Vector<Vector<double>> projectionMatrix;
258  // get constants
259  const unsigned nParticles = coupledElement.coupledParticles.size();
260  const unsigned nnode = coupledElement.el_pt->nnode();
261  const unsigned dim = coupledElement.el_pt->dim();
262 
263  // prepare projection matrix
264  projectionMatrix.resize(nnode,Vector<double>(nParticles,0.0));
265 
267  // storage for position s and shape psi
268  Vector<double> pos(dim,0.0);
269  Shape psi(nnode);
270  // loop over shape functions at the nodes
271  // loop over shape functions at the particles
272  for (unsigned p = 0; p < nParticles; p++) {
273  BaseParticle* particle = coupledElement.coupledParticles[p];
274  // get the shape function evaluated at the center of particle p
275  coupledElement.el_pt->shape(coupledParticles_[particle->getIndex()].s, psi);
276  for (unsigned n=0; n < nnode; n++) {
277  projectionMatrix[n][p] = psi(n) * particle->getVolume();
278  }
279  }
280  // normalize each row of the projection matrix to sum to one
281  for (unsigned n=0; n < nnode; n++) {
282  double sum = 0;
283  for (unsigned p = 0; p<nParticles; p++) {
284  sum += projectionMatrix[n][p];
285  }
286  if (sum != 0) {
287  for (unsigned p = 0; p < nParticles; p++) {
288  projectionMatrix[n][p] /= sum;
289  }
290  }
291  }
292  return projectionMatrix;
293  }
294 
297  {
298  // first loop over the coupled bulk el_pts
299  for (const auto& coupledElement : coupledElements_)
300  {
301  // construct projection matrix, defined as projectionMatrix_{n,p} = N_{n,p}*V_p / sum_p(N_{n,p}*V_p)
302  // (shape function N, volume V, node n, particle p)
303  Vector<Vector<double>> projectionMatrix = computeProjectionMatrix(coupledElement);
304  // get pointer to the bulk el_pt
305  ELEMENT* el_pt = coupledElement.el_pt;
306  const unsigned nParticles = coupledElement.coupledParticles.size();
307  // get number of nodes in the bulk el_pt
308  const unsigned nnode = coupledElement.el_pt->nnode();
309  // prepare vector of nodal velocities projected from particle centers
310  const unsigned dim = el_pt->dim();
311  // get coupling force at nodal positions, based on penalizing the difference in velocity
312  computeNodalCouplingForces(coupledElement, projectionMatrix);
313  // project coupling forces from nodes to particles (note projectionMatrix^{-1} = projectionMatrix^T)
314  for (unsigned p=0; p < nParticles; p++) {
315  BaseParticle* particle = coupledElement.coupledParticles[p];
316  CoupledParticle& coupledParticle = coupledParticles_[particle->getIndex()];
317  for (unsigned d=0; d < dim; d++) {
318  double forceComponent = 0;
319  for (unsigned n=0; n < nnode; n++) {
320  // note projectionMatrix^{-1} = projectionMatrix^T
321  forceComponent += projectionMatrix[n][p] * el_pt->get_coupling_residual(n, d);
322  }
323  coupledParticle.couplingForce.setComponent(d,forceComponent);
324  }
325  logger(VERBOSE,"Coupling force % on particle %", coupledParticle.couplingForce,p);
326  }
327  }
328  }
329 
331  void computeNodalCouplingForces(const CoupledElement& coupledElement, const Vector<Vector<double>>& projectionMatrix)
332  {
333  // return if no particle in cell
334  if (coupledElement.coupledParticles.empty()) return;
335 
336  // a few shortcut variables
337  const ELEMENT* el_pt = coupledElement.el_pt;
338  const unsigned nnode = el_pt->nnode();
339  const unsigned dim = el_pt->dim();
340  const unsigned nParticles = coupledElement.coupledParticles.size();
341 
343  Vector<Vector<double>> nodalVelocityDifference(nnode,Vector<double>(dim,0.0));
344  // get projected nodal velocity field from particle velocities
345  for (unsigned n=0; n < nnode; n++) {
346  for (unsigned d=0; d < dim; d++) {
347  double nodalVelocityDPM = 0;
348  for (unsigned p=0; p < nParticles; p++) {
349  nodalVelocityDPM += projectionMatrix[n][p] * coupledElement.coupledParticles[p]->getVelocity().getComponent(d);
350  }
351  double nodalVelocityFEM = el_pt->dnodal_position_gen_dt(n,0,d);
352  nodalVelocityDifference[n][d] = nodalVelocityDPM-nodalVelocityFEM;
353  }
354  }
355 
357  Vector<Vector<double> > nodalCouplingForces(nnode, Vector<double>(dim, 0.0));
358 
359  // Set up memory for the shape functions
360  Shape psi(nnode);
361  DShape dpsidxi(nnode,dim);
362  const unsigned n_in = el_pt->integral_pt()->nweight();
363  // Loop over the integration points
364  for (unsigned in=0; in<n_in; in++)
365  {
366  // Get the integral weight
367  double w = el_pt->integral_pt()->weight(in);
368  // Call the derivatives of the shape functions (and get Jacobian)
369  double J = el_pt->dshape_lagrangian_at_knot(in,psi,dpsidxi);
370  // Loop over the test functions, nodes of the element
371  for (unsigned n=0; n < nnode; n++)
372  {
373  // Loop over the nodes of the element again
374  for (unsigned nn=0; nn < nnode; nn++)
375  {
376  double volume = penalty_ * psi(nn) * psi(n) * w * J;
377 
378  for (unsigned d=0; d < dim; d++)
379  {
380  //nodalVelocityDifference[n][d] = 1/penalty_/time_pt()->dt(0);
381  double displacement = nodalVelocityDifference[n][d] * O::time_pt()->dt(0);
382  nodalCouplingForces[n][d] -= volume * displacement;
383  //logger(INFO, "w % J % psi % % displacement % nodalCouplingForces % dt %", w, J, psi(nn), psi(n), displacement, nodalCouplingForces[n][d], time_pt()->dt(0));
384  }
385  }
386  }
387  }
388 
390  coupledElement.el_pt->set_coupling_residual(nodalCouplingForces);
391 
392  logger(VERBOSE,"VelocityDifference % % %, coupling force % % % on element %, node 0, particles %",
393  nodalVelocityDifference[0][0], nodalVelocityDifference[0][1], nodalVelocityDifference[0][2],
394  nodalCouplingForces[0][0], nodalCouplingForces[0][1], nodalCouplingForces[0][2],
395  el_pt, coupledElement.coupledParticles.size());
396  }
397 
400  {
401  if (!p->isFixed())
402  {
403  // Applying the force due to gravity (F = m.g)
404  p->addForce(M::getGravity() * p->getMass());
405  // add particle coupling force 1/w_i*f_i^c to the total force
406  const CoupledParticle& cp = coupledParticles_[p->getIndex()];
407  double couplingWeight = couplingWeight_(p->getPosition().X,p->getPosition().Y,p->getPosition().Z);
408  p->addForce(cp.couplingForce / couplingWeight);
409  }
410  }
411 };
412 
413 #endif //SCALE_COUPLING_H
const unsigned n
Definition: CG3DPackingUnitTest.cpp:11
Array< double, 1, 3 > e(1./3., 0.5, 2.)
JacobiRotation< float > J
Definition: Jacobi_makeJacobi.cpp:3
LL< Log::VERBOSE > VERBOSE
Verbose information.
Definition: Logger.cc:36
Logger< MERCURYDPM_LOGLEVEL > logger("MercuryKernel")
Definition of different loggers with certain modules. A user can define its own custom logger here.
RowVector3d w
Definition: Matrix_resize_int.cpp:3
float * p
Definition: Tutorial_Map_using.cpp:9
Definition: BaseCoupling.h:27
unsigned int getIndex() const
Returns the index of the object in the handler.
Definition: BaseObject.h:97
Definition: BaseParticle.h:33
virtual Mdouble getVolume() const
Get Particle volume function, which required a reference to the Species vector. It returns the volume...
Definition: BaseParticle.cc:126
Definition: ScaleCoupling.h:19
void solveScaleCoupling(unsigned nStep)
Solve scale-coupled problem.
Definition: ScaleCoupling.h:127
void setCouplingWeight(const std::function< double(double x, double y, double z)> &couplingWeight)
Sets weight function (determines which nodes/el_pts are in coupling zone)
Definition: ScaleCoupling.h:100
void computeCouplingForce()
Computes coupling force for each element and particle.
Definition: ScaleCoupling.h:296
void updateCoupledElements()
Updates the coupledElements and coupledParticles vectors: finds which particles interact with which e...
Definition: ScaleCoupling.h:192
O::ELEMENT ELEMENT
type of el_pt in O (e.g. RefineableQDPVDElement<3, 2>)
Definition: ScaleCoupling.h:22
std::function< double(double x, double y, double z)> couplingWeight_
Definition: ScaleCoupling.h:27
std::vector< CoupledElement > coupledElements_
Vector of all coupled elements.
Definition: ScaleCoupling.h:38
const std::vector< CoupledParticle > & getCoupledParticles()
get coupled particle
Definition: ScaleCoupling.h:90
void computeOneTimeStepScaleCoupling(unsigned nStep)
What to do in each time step.
Definition: ScaleCoupling.h:184
const std::vector< CoupledElement > & getCoupledElements()
get coupled element
Definition: ScaleCoupling.h:85
void solveScaleCoupling()
Computes nStep, the ratio of FEM and DEM time steps, then calls solveSurfaceCoupling(nStep)
Definition: ScaleCoupling.h:105
std::vector< CoupledParticle > coupledParticles_
The i-th element of this vector describes the coupling properties of the i-th DPM particle.
Definition: ScaleCoupling.h:77
void computeExternalForces(BaseParticle *p) override
Applies coupling force to MercuryDPM in each time step.
Definition: ScaleCoupling.h:399
void computeNodalCouplingForces(const CoupledElement &coupledElement, const Vector< Vector< double >> &projectionMatrix)
Computes coupling force for each node.
Definition: ScaleCoupling.h:331
void setPenalty(double penalty)
Sets penalty parameter.
Definition: ScaleCoupling.h:95
void initialiseCoupledElements()
initialises the coupledElements vector
Definition: ScaleCoupling.h:158
Vector< Vector< double > > computeProjectionMatrix(const CoupledElement &coupledElement)
Computes projectionMatrix.
Definition: ScaleCoupling.h:255
Definition: Kernel/Math/Vector.h:30
Mdouble Y
Definition: Kernel/Math/Vector.h:45
Mdouble Z
Definition: Kernel/Math/Vector.h:45
Mdouble X
the vector components
Definition: Kernel/Math/Vector.h:45
void setComponent(int index, double val)
Sets the requested component of this Vec3D to the requested value.
Definition: Vector.cc:197
Definition: shape.h:278
Definition: geom_objects.h:101
Definition: shape.h:76
Definition: nodes.h:1686
#define min(a, b)
Definition: datatypes.h:22
#define max(a, b)
Definition: datatypes.h:23
Scalar * y
Definition: level1_cplx_impl.h:128
RealScalar s
Definition: level1_cplx_impl.h:130
#define isfinite(X)
Definition: main.h:111
#define INFO(i)
Definition: mumps_solver.h:54
list nParticles
Definition: MergeRestartFiles.py:46
const Mdouble NaN
Definition: GeneralDefine.h:22
const Mdouble inf
Definition: GeneralDefine.h:23
DRAIG: Change all instances of (SPATIAL_DIM) to (DIM-1).
Definition: AnisotropicHookean.h:10
list x
Definition: plotDoE.py:28
Stores properties of a coupled element: pointer to the element and list of particles coupled to it.
Definition: ScaleCoupling.h:30
std::vector< BaseParticle * > coupledParticles
Definition: ScaleCoupling.h:34
ELEMENT * el_pt
Definition: ScaleCoupling.h:32
For all particles, stores coupling properties: coupling force, pointer to coupled element and locatio...
Definition: ScaleCoupling.h:41
CoupledElement * coupledElement_pt
Element in which this particle resides (null if particle is not coupled)
Definition: ScaleCoupling.h:45
Vec3D couplingForce
Coupling force.
Definition: ScaleCoupling.h:43
void setCoupledElement(CoupledElement *coupledElementNew, BaseParticle *particle)
Remove particle from coupledParticle.coupledElement.particles and set coupledParticle....
Definition: ScaleCoupling.h:59
void removeCoupledElement(BaseParticle *particle)
Removes particle from coupledParticle.coupledElement.particles and sets coupledParticle....
Definition: ScaleCoupling.h:50
Vector< double > s
Location of particle in coupled element, returned by locate zeta.
Definition: ScaleCoupling.h:47