version 3.11-dev
fokkerplanck.hh
Go to the documentation of this file.
1// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2// vi: set et ts=4 sw=4 sts=4:
3//
4// SPDX-FileCopyrightText: Copyright © DuMux Project contributors, see AUTHORS.md in root folder
5// SPDX-License-Identifier: GPL-3.0-or-later
6//
12#include <memory>
13#include <vector>
14#include <array>
15#include <random>
16#include <cmath>
17#include <iostream>
18
19#include <dune/common/fvector.hh>
20#include <dune/common/exceptions.hh>
21
22
24
27
30
32
79template<class Scalar, class GridGeometry>
81{
82 static constexpr int dim = GridGeometry::GridView::dimension;
83 static constexpr int dimWorld = GridGeometry::GridView::dimensionworld;
84
87 using GlobalPosition = Particle::GlobalPosition;
88 using Velocity = Dune::FieldVector<Scalar, dim>;
89
90 static_assert(GridGeometry::discMethod == DiscretizationMethods::cctpfa, "Implementation currently assumes a CC-TPFA grid geometry");
91public:
92 FokkerPlanck(std::shared_ptr<const GridGeometry> gg, std::shared_ptr<Cloud> cloud)
93 : gridGeometry_(std::move(gg))
94 , cloud_(std::move(cloud))
95 , rndGen_(std::random_device{}())
96 {
97 if (cloud_->size(false) == 0)
98 cloud_->resize(getParam<int>("FokkerPlanck.NumParticles"));
99
100 const auto numParticles = cloud_->size(false);
101 diffusionCoefficient_ = getParam<Scalar>("FokkerPlanck.DiffusionCoefficient");
102 data_.resize(numParticles);
103 particleMass_ = getParam<Scalar>("FokkerPlanck.ParticleMass", 1.0/numParticles);
104 volumes_.resize(gridGeometry_->gridView().size(0), 0.0);
105 for (const auto& element : elements(gridGeometry_->gridView()))
106 {
107 const auto eIdx = gridGeometry_->elementMapper().index(element);
108 const auto geometry = element.geometry();
109 volumes_[eIdx] = geometry.volume();
110 }
111 }
112
119 {
120 std::size_t eIdx; // in which element of the grid this particle is
121 Scalar time; // at which time the particle lives
122 };
123
128 template<class Func>
129 void init(const Func& func)
130 {
131 cloud_->forEachParticle([&](auto& p){
132 func(p, data_[p.id()]);
133 });
134 }
135
151 void run(const Scalar dt)
152 {
153 if (velocities_.empty())
154 DUNE_THROW(Dune::InvalidStateException, "A velocity field must be set before calling run");
155
156 // move the particles with the given velocity field through the grid
157 cloud_->forEachActiveParticle([&](auto& p){
158 moveParticle_(p, dt);
159 });
160 }
161
176 void computeDensity(std::vector<Scalar>& density) const
177 {
178 density.assign(gridGeometry_->gridView().size(0), 0.0);
179 for (const auto& p : particles(*cloud_))
180 {
181 const auto eIdx = data_[p.id()].eIdx;
182 density[eIdx] += particleMass_/volumes_[eIdx];
183 }
184 }
185
191 void setVelocityData(const std::vector<Velocity>& velocities)
192 {
193 if (velocities.size() != gridGeometry_->gridView().size(0) && velocities.size() != 1)
194 DUNE_THROW(Dune::InvalidStateException, "Velocity data size does not match number of elements or 1");
195 velocities_ = velocities;
196 }
197
199 std::shared_ptr<const Cloud> particleCloud() const
200 { return cloud_; }
201
202private:
203 void moveParticle_(Particle& p, const Scalar dt)
204 {
205 auto& particleData = data_[p.id()];
206
207 // (1) Drift: time loop until time integration is finished
208 particleData.time = dt; // recharge time
209 int count = 0;
210 while (particleData.time > 0.0 && velocity_(particleData.eIdx).two_norm2() > 0.0)
211 {
212 const auto tStartIteration = particleData.time;
213
214 const auto velocityInGrid = velocity_(particleData.eIdx);
215 moveParticleInGridElement_(p, velocityInGrid);
216
217 count = particleData.time < tStartIteration ? 0 : count + 1;
218 if (count > 4)
219 {
220 std::cout << "Particle [" << p.id() << "] at " << p.position() << " in element " << particleData.eIdx << ", t=" << particleData.time << ", velocity: " << velocityInGrid << "\n";
221 std::cout << "-- during advection step: particle got stuck and is deactivated\n";
222
223 cloud_->deactivateParticle(p);
224 particleData.time = -1.0;
225 }
226 }
227
228 static_assert(dim == dimWorld, "Not implemented yet for dim != dimWorld");
229
230 // (2) Brownian motion: time loop until time integration is finished
231 particleData.time = 1.0; // recharge time
232 const auto velocityInGrid = [&]{
233 const auto scale = std::sqrt(2.0*diffusionCoefficient_);
234 Dune::FieldVector<Scalar, dimWorld> newPos(p.position());
235 for (int i = 0; i < dim; ++i)
236 newPos[i] += scale * std::normal_distribution<Scalar>(0.0, std::sqrt(dt))(rndGen_);
237 return newPos - p.position();
238 }();
239
240 const auto velocityNorm = velocityInGrid.two_norm2();
241 count = 0;
242 while (particleData.time > 0.0 && velocityNorm > 0.0)
243 {
244 const auto tStartIteration = particleData.time;
245
246 moveParticleInGridElement_(p, velocityInGrid);
247
248 count = particleData.time < tStartIteration ? 0 : count + 1;
249 if (count > 4)
250 {
251 std::cout << "Particle [" << p.id() << "] at " << p.position() << " in element " << particleData.eIdx << ", t=" << particleData.time << ", velocity: " << velocityInGrid << "\n";
252 std::cout << "-- during diffusion step: particle got stuck and is deactivated\n";
253
254 cloud_->deactivateParticle(p);
255 particleData.time = -1.0;
256 }
257 }
258 }
259
260 void moveParticleInGridElement_(Particle& p, const Velocity& velocity)
261 {
262 auto& particleData = data_[p.id()];
263 assert(particleData.time > 0.0);
264
265 const auto eIdx = particleData.eIdx;
266 if (eIdx >= gridGeometry_->gridView().size(0))
267 DUNE_THROW(Dune::InvalidStateException, "Element index is invalid");
268
269 const auto& element = gridGeometry_->element(eIdx);
270 const auto geometry = element.geometry();
271
272 const auto startPos = p.position();
273 auto endPos = startPos;
274 endPos.axpy(particleData.time, velocity);
275
276 // we stay inside this element
277 if (intersectsPointGeometry(endPos, geometry))
278 {
279 p.setPosition(endPos);
280 particleData.time = -1.0; // this particle is done
281 return;
282 }
283
284 // increase the change of finding the intersection
285 // if we are right on the element facet by backtracking a bit
286 auto beforeStart = startPos;
287 beforeStart.axpy(-1e-4*particleData.time, velocity);
288
289 const auto fvGeometry = localView(*gridGeometry_).bind(element);
290 Dune::AffineGeometry<double, 1, dimWorld> segGeo(Dune::GeometryTypes::line, std::array<GlobalPosition, 2>{{beforeStart, endPos}});
292 using IntersectionAlgorithm = GeometryIntersection<Dune::AffineGeometry<double, 1, dimWorld>, decltype(fvGeometry.geometry(fvGeometry.scvf(0))), IP>;
293 using Intersection = typename IntersectionAlgorithm::Intersection;
294 Intersection intersection;
295
296 static_assert(dim >= 1, "Particle transport only makes sense for 1D and higher");
297 const auto intersectionFound = [&](const auto& scvf, Intersection& intersection)
298 {
299 if constexpr (dim == 1)
300 {
301 const bool found = intersectsPointGeometry(scvf.ipGlobal(), segGeo);
302 if (found) intersection = scvf.ipGlobal();
303 return found;
304 }
305 else
306 return IntersectionAlgorithm::intersection(
307 segGeo, fvGeometry.geometry(scvf), intersection
308 );
309 };
310
311 for (const auto& scvf : scvfs(fvGeometry))
312 {
313 if (intersectionFound(scvf, intersection))
314 {
315 // for now we deactivate the particle if it hits a boundary
316 if (scvf.boundary())
317 {
318 cloud_->deactivateParticle(p);
319 particleData.time = -1.0; // this particle is done
320 return;
321 }
322
323 // corner case (intersects more than one scvfs)
324 if (scvf.unitOuterNormal()*velocity < 1e-4*velocity.two_norm())
325 continue;
326
327 // put particle on face into the neighboring element
328 const auto velocityNorm = velocity.two_norm2();
329 const auto distance = (intersection-startPos).two_norm2();
330 intersection.axpy(1e-3, endPos-startPos);
331 p.setPosition(intersection);
332 particleData.eIdx = scvf.outsideScvIdx();
333 particleData.time -= std::sqrt(distance/velocityNorm);
334 return;
335 }
336 }
337
338 // particle got lost: try to find the particle in the grid
339 static constexpr bool isCartesianGrid = Dune::Capabilities::isCartesian<typename GridGeometry::GridView::Grid>::v;
340 const auto entities = intersectingEntities(p.position(), gridGeometry_->boundingBoxTree(), isCartesianGrid);
341 if (entities.empty()) // outside the grid
342 {
343 cloud_->deactivateParticle(p);
344 particleData.time = -1.0; // this particle is done
345 return;
346 }
347
348 // found particle in an element, try to move on from there
349 particleData.eIdx = entities[0];
350 }
351
352 const Velocity& velocity_(std::size_t eIdx) const
353 {
354 if (velocities_.size() == 1)
355 return velocities_[0];
356 return velocities_[eIdx];
357 }
358
359 std::shared_ptr<const GridGeometry> gridGeometry_;
360 std::vector<ParticleData> data_;
361 std::shared_ptr<Cloud> cloud_;
362 std::mt19937 rndGen_;
363
364 std::vector<Velocity> velocities_;
365 Scalar diffusionCoefficient_;
366
367 std::vector<Scalar> volumes_;
368 Scalar particleMass_;
369
370};
371
372} // end namespace Dumux::Particles
A particle solver for the Fokker-Planck equation.
Definition: fokkerplanck.hh:81
void run(const Scalar dt)
Evolve particle state with Euler scheme.
Definition: fokkerplanck.hh:151
void setVelocityData(const std::vector< Velocity > &velocities)
set the velocity data for each grid element
Definition: fokkerplanck.hh:191
std::shared_ptr< const Cloud > particleCloud() const
access the underlying particle cloud
Definition: fokkerplanck.hh:199
FokkerPlanck(std::shared_ptr< const GridGeometry > gg, std::shared_ptr< Cloud > cloud)
Definition: fokkerplanck.hh:92
void init(const Func &func)
For each particle apply a function to initialize it's state.
Definition: fokkerplanck.hh:129
void computeDensity(std::vector< Scalar > &density) const
computes the particle density in the grid
Definition: fokkerplanck.hh:176
a basic particle
Definition: particle.hh:25
Dune::FieldVector< CoordScalar, dimWorld > GlobalPosition
Definition: particle.hh:29
const GlobalPosition & position() const
Definition: particle.hh:37
std::size_t id() const
Definition: particle.hh:40
A simple implementation of a cloud of particles.
Definition: simpleparticlecloud.hh:36
A class for collision detection of two geometries and computation of intersection corners.
GridCache::LocalView localView(const GridCache &gridCache)
Free function to get the local view of a grid cache object.
Definition: localview.hh:26
bool intersectsPointGeometry(const Dune::FieldVector< ctype, dimworld > &point, const Geometry &g)
Find out whether a point is inside a three-dimensional geometry.
Definition: intersectspointgeometry.hh:28
static ctype distance(const Dune::FieldVector< ctype, dimWorld > &a, const Dune::FieldVector< ctype, dimWorld > &b)
Compute the shortest distance between two points.
Definition: distance.hh:282
std::vector< std::size_t > intersectingEntities(const Dune::FieldVector< ctype, dimworld > &point, const BoundingBoxTree< EntitySet > &tree, bool isCartesianGrid=false)
Compute all intersections between entities and a point.
Definition: intersectingentities.hh:102
Algorithms that finds which geometric entities intersect.
constexpr CCTpfa cctpfa
Definition: method.hh:145
constexpr Line line
Definition: couplingmanager1d3d_line.hh:31
std::string density(int phaseIdx) noexcept
I/O name of density for multiphase systems.
Definition: name.hh:53
Definition: fokkerplanck.hh:31
Parallel for loop (multithreading)
A simple particle.
A simple implementation of a cloud of particles.
Policy structure for point-like intersections.
Definition: geometryintersection.hh:34
data associated with each particle id
Definition: fokkerplanck.hh:119
Scalar time
Definition: fokkerplanck.hh:121
std::size_t eIdx
Definition: fokkerplanck.hh:120