Program Listing for File particle_coupling.h¶
↰ Return to documentation for file (include/immersx/coupling/particle_coupling.h)
// ---------------------------------------------------------------------
//
// Copyright (C) 2024 by Luca Heltai
//
// This file is part of the ImmersX application, based on
// the deal.II library.
//
// The ImmersX application is free software; you can use
// it, redistribute it, and/or modify it under the terms of the Apache-2.0
// License WITH LLVM-exception as published by the Free Software Foundation;
// either version 3.0 of the License, or (at your option) any later version. The
// full text of the license can be found in the file LICENSE.md at the top level
// of the ImmersX distribution.
//
// ---------------------------------------------------------------------
#ifndef rdl_particle_coupling_h
#define rdl_particle_coupling_h
#include <deal.II/base/bounding_box.h>
#include <deal.II/base/parameter_acceptor.h>
#include <deal.II/distributed/fully_distributed_tria.h>
#include <deal.II/distributed/tria.h>
#include <deal.II/fe/fe_q.h>
#include <deal.II/fe/mapping_q1.h>
#include <deal.II/grid/grid_tools_cache.h>
#include <deal.II/particles/data_out.h>
#include <deal.II/particles/particle_handler.h>
#include <deal.II/particles/utilities.h>
#include <boost/serialization/map.hpp>
#include <boost/serialization/vector.hpp>
#include <immersx/coupling/detail/coupling_point.h>
#include <cstdint>
#include <limits>
#include <map>
#include <vector>
using namespace dealii;
namespace ImmersX
{
template <int dim>
class ParticleCouplingParameters : public ParameterAcceptor
{
public:
explicit ParticleCouplingParameters(
const std::string &subsection = "Particle coupling");
unsigned int rtree_extraction_level = 1;
};
template <int dim>
class ParticleCoupling
{
public:
ParticleCoupling(const ParticleCouplingParameters<dim> &par);
void
output_particles(const std::string &output_name) const;
void
initialize_particle_handler(
const parallel::TriangulationBase<dim> &tria_background,
const Mapping<dim> &mapping = StaticMappingQ1<dim>::mapping,
const unsigned int n_properties = 1);
std::vector<std::vector<BoundingBox<dim>>>
get_global_bounding_boxes() const;
const Particles::ParticleHandler<dim> &
get_particles() const;
std::map<unsigned int, IndexSet>
insert_points(const std::vector<Point<dim>> &points,
const std::vector<std::vector<double>> &properties = {},
const std::vector<types::particle_index> &ids = {});
protected:
const ParticleCouplingParameters<dim> ∥
MPI_Comm mpi_communicator;
ObserverPointer<const parallel::TriangulationBase<dim>> tria_background;
ObserverPointer<const Mapping<dim>> mapping;
std::vector<std::vector<BoundingBox<dim>>> global_bounding_boxes;
Particles::ParticleHandler<dim> particles;
};
namespace detail
{
template <int spacedim>
struct LiftedSourceStencil
{
types::particle_index stable_id =
std::numeric_limits<types::particle_index>::max();
types::global_cell_index source_entity_id = numbers::invalid_unsigned_int;
unsigned int representative_qpoint = numbers::invalid_unsigned_int;
unsigned int section_qpoint = numbers::invalid_unsigned_int;
Point<spacedim> representative_point;
double physical_weight = 0.;
unsigned int source_rank = numbers::invalid_unsigned_int;
std::size_t source_local_index = 0;
std::vector<types::global_dof_index> source_dof_indices;
std::vector<double> source_basis_values;
template <class Archive>
void
serialize(Archive &archive, const unsigned int)
{
archive &stable_id;
archive &source_entity_id;
archive &representative_qpoint;
archive §ion_qpoint;
for (unsigned int d = 0; d < spacedim; ++d)
archive &representative_point[d];
archive &physical_weight;
archive &source_rank;
archive &source_local_index;
archive &source_dof_indices;
archive &source_basis_values;
}
};
} // namespace detail
template <int spacedim>
class DistributedLiftedQuadrature
{
public:
using Point = detail::CouplingPoint<spacedim, double>;
using Stencil = detail::LiftedSourceStencil<spacedim>;
explicit DistributedLiftedQuadrature(
const ParticleCouplingParameters<spacedim> ¶meters)
: particle_coupling_(parameters)
{}
template <typename PointType>
void
initialize(const parallel::TriangulationBase<spacedim> &target_tria,
const Mapping<spacedim> &target_mapping,
const std::vector<PointType> &source_points)
{
communicator_ = target_tria.get_mpi_communicator();
source_ids_by_target_.clear();
std::vector<dealii::Point<spacedim>> positions;
std::vector<types::particle_index> ids;
source_stencils_.clear();
positions.reserve(source_points.size());
ids.reserve(source_points.size());
for (const auto &source_point : source_points)
{
AssertThrow(
source_point.stable_id != std::numeric_limits<std::uint64_t>::max(),
ExcMessage(
"Distributed lifted coupling requires a stable point id."));
const auto id =
static_cast<types::particle_index>(source_point.stable_id);
AssertThrow(source_stencils_.find(id) == source_stencils_.end(),
ExcMessage("Stable lifted point ids must be unique."));
Stencil stencil;
stencil.stable_id = id;
stencil.source_entity_id = source_point.source_entity_id;
stencil.representative_qpoint = source_point.representative_qpoint;
stencil.section_qpoint = source_point.section_qpoint;
stencil.representative_point = source_point.representative_point;
stencil.physical_weight = source_point.weight;
stencil.source_rank = Utilities::MPI::this_mpi_process(
target_tria.get_mpi_communicator());
stencil.source_local_index =
static_cast<std::size_t>(&source_point - source_points.data());
stencil.source_dof_indices = source_point.dof_indices;
stencil.source_basis_values.assign(source_point.basis_values.begin(),
source_point.basis_values.end());
source_stencils_.emplace(id, std::move(stencil));
positions.push_back(source_point.point);
ids.push_back(id);
}
particle_coupling_.initialize_particle_handler(target_tria,
target_mapping,
0);
const auto receiving_ranks =
particle_coupling_.insert_points(positions, {}, ids);
// On a receiving rank, receiving_ranks is keyed by source rank and
// contains source-local point indices. Exchange that request map back to
// the source ranks, which can then send only the required stencils.
const auto target_to_indices =
Utilities::MPI::some_to_some(target_tria.get_mpi_communicator(),
receiving_ranks);
std::map<unsigned int, std::vector<Stencil>> stencils_by_target;
for (const auto &[target_rank, indices] : target_to_indices)
{
auto &stencils = stencils_by_target[target_rank];
auto &ids_for_target = source_ids_by_target_[target_rank];
stencils.reserve(indices.n_elements());
ids_for_target.reserve(indices.n_elements());
for (const auto index : indices)
{
AssertIndexRange(index, source_points.size());
const auto id = ids[index];
ids_for_target.push_back(id);
stencils.push_back(source_stencils_.at(id));
}
}
const auto source_to_stencils =
Utilities::MPI::some_to_some(target_tria.get_mpi_communicator(),
stencils_by_target);
target_stencils_.clear();
for (const auto &[unused_source_rank, stencils] : source_to_stencils)
{
(void)unused_source_rank;
for (const auto &stencil : stencils)
target_stencils_[stencil.stable_id] = stencil;
}
}
std::map<types::particle_index, double>
values_on_target(const dealii::Vector<double> &source_values) const
{
// This is the forward redistribution D. Geometry and source stencils
// were exchanged by initialize(); repeated applications only exchange
// values.
std::map<unsigned int, std::map<types::particle_index, double>>
values_by_target;
for (const auto &[target_rank, ids] : source_ids_by_target_)
for (const auto id : ids)
values_by_target[target_rank][id] =
source_values[source_stencils_.at(id).source_local_index];
const auto received =
Utilities::MPI::some_to_some(communicator_, values_by_target);
std::map<types::particle_index, double> result;
for (const auto &[unused_source_rank, values] : received)
{
(void)unused_source_rank;
result.insert(values.begin(), values.end());
}
return result;
}
void
add_transpose_to_source(
const std::map<types::particle_index, double> &target_values,
dealii::Vector<double> &source_values) const
{
// This is D^T, the transpose of the value redistribution above. It is
// separate from the tensor-product lift transpose and does not rebuild
// or modify the exchanged geometry stencils.
std::map<unsigned int, std::map<types::particle_index, double>>
values_by_source;
for (const auto &[id, value] : target_values)
values_by_source[target_stencils_.at(id).source_rank][id] += value;
const auto received =
Utilities::MPI::some_to_some(communicator_, values_by_source);
for (const auto &[unused_target_rank, values] : received)
{
(void)unused_target_rank;
for (const auto &[id, value] : values)
source_values[source_stencils_.at(id).source_local_index] += value;
}
}
const ParticleCoupling<spacedim> &
particle_coupling() const
{
return particle_coupling_;
}
const std::map<types::particle_index, Stencil> &
target_stencils() const
{
return target_stencils_;
}
const Stencil &
stencil(const types::particle_index id) const
{
AssertThrow(target_stencils_.find(id) != target_stencils_.end(),
ExcMessage("No source stencil was received for particle."));
return target_stencils_.at(id);
}
private:
ParticleCoupling<spacedim> particle_coupling_;
MPI_Comm communicator_ = MPI_COMM_WORLD;
std::map<types::particle_index, Stencil> source_stencils_;
std::map<types::particle_index, Stencil> target_stencils_;
std::map<unsigned int, std::vector<types::particle_index>>
source_ids_by_target_;
};
} // namespace ImmersX
#endif