Files
mfem/fem/particleset.cpp
T

951 lines
25 KiB
C++

// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
// LICENSE and NOTICE for details. LLNL-CODE-806117.
//
// This file is part of the MFEM library. For more information and source code
// availability visit https://mfem.org.
//
// MFEM is free software; you can redistribute it and/or modify it under the
// terms of the BSD-3 license. We welcome feedback and contributions, see file
// CONTRIBUTING.md for details.
#include "particleset.hpp"
#if defined(MFEM_USE_MPI) && defined(MFEM_USE_GSLIB)
// Ignore warnings from the gslib header (GCC version)
#ifdef MFEM_HAVE_GCC_PRAGMA_DIAGNOSTIC
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#endif
namespace gslib
{
extern "C"
{
#include <gslib.h>
} // extern C
} // namespace gslib
#ifdef MFEM_HAVE_GCC_PRAGMA_DIAGNOSTIC
#pragma GCC diagnostic pop
#endif
#endif // MFEM_USE_MPI && MFEM_USE_GSLIB
namespace mfem
{
Particle::Particle(int dim, const Array<int> &field_vdims, int num_tags)
: coords(dim), fields(), tags()
{
coords = 0.0;
fields.reserve(field_vdims.Size());
for (int f = 0; f < field_vdims.Size(); f++)
{
fields.emplace_back(field_vdims[f]);
fields.back() = 0.0;
}
tags.reserve(num_tags);
for (int t = 0; t < num_tags; t++)
{
tags.emplace_back(1);
tags.back()[0] = 0;
}
}
void Particle::SetTagRef(int t, int *tag_data)
{
MFEM_ASSERT(t >= 0 &&
static_cast<size_t>(t) < tags.size(), "Invalid tag index");
tags[t].MakeRef(tag_data, 1);
}
void Particle::SetFieldRef(int f, real_t *field_data)
{
MFEM_ASSERT(f >= 0 &&
static_cast<size_t>(f) < fields.size(), "Invalid field "
"index");
Vector temp(field_data, fields[f].Size());
fields[f].MakeRef(temp, 0, fields[f].Size());
}
bool Particle::operator==(const Particle &rhs) const
{
// Compare coordinate size and values
if (coords.Size() != rhs.coords.Size())
{
return false;
}
for (int d = 0; d < coords.Size(); d++)
{
if (coords[d] != rhs.coords[d])
{
return false;
}
}
// Compare fields vdim and values
if (fields.size() != rhs.fields.size())
{
return false;
}
for (size_t f = 0; f < fields.size(); f++)
{
if (fields[f].Size() != rhs.fields[f].Size())
{
return false;
}
for (int c = 0; c < fields[f].Size(); c++)
{
if (fields[f][c] != rhs.fields[f][c])
{
return false;
}
}
}
// Compare tags size and values
if (tags.size() != rhs.tags.size())
{
return false;
}
for (size_t t = 0; t < tags.size(); t++)
{
if (tags[t][0] != rhs.tags[t][0])
{
return false;
}
}
return true;
}
void Particle::Print(std::ostream &os) const
{
os << "Coords: (";
for (int d = 0; d < coords.Size(); d++)
{
os << coords[d] << ( (d+1 < coords.Size()) ? "," : ")\n");
}
for (size_t f = 0; f < fields.size(); f++)
{
os << "Field " << f << ": (";
for (int c = 0; c < fields[f].Size(); c++)
{
os << fields[f][c] << ( (c+1 < fields[f].Size()) ? "," : ")\n");
}
}
for (size_t t = 0; t < tags.size(); t++)
{
os << "Tag " << t << ": " << tags[t][0] << "\n";
}
}
Array<Ordering::Type> ParticleSet::GetOrderingArray(Ordering::Type o, int N)
{
Array<Ordering::Type> ordering_arr(N);
ordering_arr = o;
return ordering_arr;
}
std::string ParticleSet::GetDefaultFieldName(int i)
{
return "Field_" + std::to_string(i);
}
std::string ParticleSet::GetDefaultTagName(int i)
{
return "Tag_" + std::to_string(i);
}
Array<const char*> ParticleSet::GetEmptyNameArray(int N)
{
Array<const char*> names(N);
names = nullptr;
return names;
}
#ifdef MFEM_USE_MPI
int ParticleSet::GetRank(MPI_Comm comm_)
{
int r; MPI_Comm_rank(comm_, &r);
return r;
}
int ParticleSet::GetSize(MPI_Comm comm_)
{
int s; MPI_Comm_size(comm_, &s);
return s;
}
#endif // MFEM_USE_MPI
void ParticleSet::Reserve(int res)
{
ids.Reserve(res);
// Reserve fields
for (int f = -1; f < GetNFields(); f++)
{
ParticleVector &pv = (f == -1 ? coords : *fields[f]);
pv.Reserve(res*pv.GetVDim());
}
// Reserve tags
for (int t = 0; t < GetNTags(); t++)
{
tags[t]->Reserve(res);
}
}
const Array<int> ParticleSet::GetFieldVDims() const
{
Array<int> field_vdims(GetNFields());
for (int f = 0; f < GetNFields(); f++)
{
field_vdims[f] = Field(f).GetVDim();
}
return field_vdims;
}
void ParticleSet::AddParticles(const Array<IDType> &new_ids,
Array<int> *new_indices)
{
int num_add = new_ids.Size();
int old_np = GetNParticles();
int new_np = old_np + num_add;
// Set indices of new particles
if (new_indices)
{
new_indices->SetSize(num_add);
for (int i = 0; i < num_add; i++)
{
(*new_indices)[i] = ids.Size() + i;
}
}
// Add new ids
ids.Append(new_ids);
// Update data
for (int f = -1; f < GetNFields(); f++)
{
ParticleVector &pv = (f == -1 ? coords : *fields[f]);
pv.SetNumParticles(new_np); // does not delete existing data
}
// Update tags
for (int t = 0; t < GetNTags(); t++)
{
tags[t]->SetSize(new_np);
}
}
#if defined(MFEM_USE_MPI) && defined(MFEM_USE_GSLIB)
/// \cond DO_NOT_DOCUMENT
template<size_t NBytes>
void ParticleSet::TransferParticlesImpl(ParticleSet &pset,
const Array<int> &send_idxs,
const Array<unsigned int> &send_ranks)
{
struct pdata_t
{
alignas(real_t) std::array<std::byte, NBytes> data;
IDType id;
};
int nreals = pset.GetFieldVDims().Sum() + pset.Coords().GetVDim();
int ntags = pset.GetNTags();
size_t nbytes = nreals*sizeof(real_t) + ntags*sizeof(int);
MFEM_VERIFY(nbytes <= NBytes, "More data than can be packed.");
using parr_t = pdata_t;
gslib::array gsl_arr;
parr_t *pdata_arr;
array_init(parr_t, &gsl_arr, send_idxs.Size());
pdata_arr = (parr_t*) gsl_arr.ptr;
gsl_arr.n = send_idxs.Size();
for (int i = 0; i < send_idxs.Size(); i++)
{
parr_t &pdata = pdata_arr[i];
pdata.id = pset.GetIDs()[send_idxs[i]];
// Copy particle data directly into pdata
size_t counter = 0;
for (int f = -1; f < pset.GetNFields(); f++)
{
ParticleVector &pv = (f == -1 ? pset.Coords() : pset.Field(f));
for (int c = 0; c < pv.GetVDim(); c++)
{
std::memcpy(pdata.data.data() + counter, &pv(send_idxs[i], c),
sizeof(real_t));
counter += sizeof(real_t);
}
}
// Copy tags
for (int t = 0; t < pset.GetNTags(); t++)
{
Array<int> &tag_arr = pset.Tag(t);
std::memcpy(pdata.data.data() + counter, &tag_arr[send_idxs[i]],
sizeof(int));
counter += sizeof(int);
}
}
int nparticles = pset.GetNParticles();
int nsend = send_idxs.Size();
// Transfer particles
sarray_transfer_ext(parr_t, &gsl_arr, send_ranks.GetData(),
sizeof(unsigned int), pset.cr);
// Make sure we have enough space for received particles
int nrecv = (int) gsl_arr.n;
int ndelete = nsend - nrecv;
if (ndelete > 0)
{
// Remove unneeded particles
auto datap = const_cast<int*>(send_idxs.GetData());
Array<int> delete_idxs(datap + nrecv, ndelete);
pset.RemoveParticles(delete_idxs);
}
else
{
pset.Reserve(nparticles-ndelete);
}
pdata_arr = (parr_t*) gsl_arr.ptr;
// Add newly-recvd data directly to active state
for (int i = 0; i < nrecv; i++)
{
parr_t &pdata = pdata_arr[i];
IDType id = pdata.id;
int new_loc_idx;
if (i < nsend) // update existing particle
{
new_loc_idx = send_idxs[i];
pset.UpdateID(new_loc_idx, id);
}
else
{
// add new particle
Array<int> idx_temp;
pset.AddParticles(Array<IDType>({id}), &idx_temp);
new_loc_idx = idx_temp[0]; // Get index of newly-added particle
}
size_t counter = 0;
for (int f = -1; f < pset.GetNFields(); f++)
{
ParticleVector &pv = (f == -1 ? pset.Coords() : pset.Field(f));
for (int c = 0; c < pv.GetVDim(); c++)
{
real_t& val = pv(new_loc_idx, c);
std::memcpy(&val, pdata.data.data() + counter, sizeof(real_t));
counter += sizeof(real_t);
}
}
for (int t = 0; t < pset.GetNTags(); t++)
{
Array<int> &tag_arr = pset.Tag(t);
std::memcpy(&tag_arr[new_loc_idx],
pdata.data.data() + counter, sizeof(int));
counter += sizeof(int);
}
}
array_free(&gsl_arr);
}
template<size_t NBytes>
ParticleSet::TransferParticlesType ParticleSet::TransferParticles::Kernel()
{
return &ParticleSet::TransferParticlesImpl<NBytes>;
}
ParticleSet::Kernels::Kernels()
{
constexpr size_t sizd = sizeof(real_t);
TransferParticles::Specialization<2*sizd>::Add();
TransferParticles::Specialization<3*sizd>::Add();
TransferParticles::Specialization<4*sizd>::Add();
TransferParticles::Specialization<8*sizd>::Add();
TransferParticles::Specialization<12*sizd>::Add();
TransferParticles::Specialization<16*sizd>::Add();
TransferParticles::Specialization<20*sizd>::Add();
TransferParticles::Specialization<24*sizd>::Add();
TransferParticles::Specialization<28*sizd>::Add();
TransferParticles::Specialization<32*sizd>::Add();
TransferParticles::Specialization<36*sizd>::Add();
TransferParticles::Specialization<40*sizd>::Add();
}
auto ParticleSet::TransferParticles::Fallback(size_t bufsize)
-> ParticleSet::TransferParticlesType
{
constexpr size_t sizd = sizeof(real_t);
if (bufsize < 4*sizd)
{
return &ParticleSet::TransferParticlesImpl<4*sizd>;
}
else if (bufsize < 8*sizd)
{
return &ParticleSet::TransferParticlesImpl<8*sizd>;
}
else if (bufsize < 12*sizd)
{
return &ParticleSet::TransferParticlesImpl<12*sizd>;
}
else if (bufsize < 16*sizd)
{
return &ParticleSet::TransferParticlesImpl<16*sizd>;
}
else if (bufsize < 20*sizd)
{
return &ParticleSet::TransferParticlesImpl<20*sizd>;
}
else if (bufsize < 24*sizd)
{
return &ParticleSet::TransferParticlesImpl<24*sizd>;
}
else if (bufsize < 28*sizd)
{
return &ParticleSet::TransferParticlesImpl<28*sizd>;
}
else if (bufsize < 32*sizd)
{
return &ParticleSet::TransferParticlesImpl<32*sizd>;
}
else if (bufsize < 36*sizd)
{
return &ParticleSet::TransferParticlesImpl<36*sizd>;
}
else if (bufsize < 40*sizd)
{
return &ParticleSet::TransferParticlesImpl<40*sizd>;
}
return &ParticleSet::TransferParticlesImpl<60*sizd>;
}
/// \endcond DO_NOT_DOCUMENT
void ParticleSet::Redistribute(const Array<unsigned int> &rank_list)
{
MFEM_ASSERT(rank_list.Size() == GetNParticles(),
"rank_list must be of size GetNParticles().");
int rank = GetRank(comm);
// Get particles to be transferred
// (Avoid unnecessary copies of particle data into and out of buffers)
Array<int> send_idxs;
Array<unsigned int> send_ranks;
send_idxs.Reserve(rank_list.Size());
send_ranks.Reserve(rank_list.Size());
for (int i = 0; i < rank_list.Size(); i++)
{
if (rank != static_cast<int>(rank_list[i]))
{
send_idxs.Append(i);
send_ranks.Append(rank_list[i]);
}
}
// Compute number of bytes of a single particle
int nreals = GetFieldVDims().Sum() + coords.GetVDim();
int ntags = GetNTags();
size_t nbytes = nreals*sizeof(real_t) + ntags*sizeof(int);
// Dispatch to appropriate redistribution function for this size
TransferParticles::Run(nbytes, *this, send_idxs, send_ranks);
}
#endif // MFEM_USE_MPI && MFEM_USE_GSLIB
Particle ParticleSet::CreateParticle() const
{
return Particle(GetDim(), GetFieldVDims(), GetNTags());
}
void ParticleSet::WriteToFile(const char *fname,
const std::stringstream &ss_header, const std::stringstream &ss_data)
{
#ifdef MFEM_USE_MPI
// Parallel:
int rank = GetRank(comm);
MPI_File_delete(fname, MPI_INFO_NULL); // delete old file if it exists
MPI_File file;
int mpi_err = MPI_File_open(comm, fname, MPI_MODE_CREATE | MPI_MODE_WRONLY,
MPI_INFO_NULL, &file);
MFEM_VERIFY(mpi_err == MPI_SUCCESS, "MPI_File_open failed.");
// Print header
if (rank == 0)
{
MPI_File_write_at(file, 0, ss_header.str().data(), ss_header.str().size(),
MPI_CHAR, MPI_STATUS_IGNORE);
}
// Compute the data size in bytes
MPI_Offset data_size = ss_data.str().size();
MPI_Offset offset;
// Compute the offsets using an exclusive scan
MPI_Exscan(&data_size, &offset, 1, MPI_OFFSET, MPI_SUM, comm);
if (rank == 0)
{
offset = 0;
}
// Add offset from the header
offset += ss_header.str().size();
// Write data collectively
MPI_File_write_at_all(file, offset, ss_data.str().data(),
data_size, MPI_BYTE, MPI_STATUS_IGNORE);
// Close file
MPI_File_close(&file);
#else
// Serial:
std::ofstream ofs(fname);
MFEM_VERIFY(ofs.is_open() && !ofs.fail(),
"Error: Could not open file " << fname << " for writing.");
ofs << ss_header.str() << ss_data.str();
ofs.close();
#endif // MFEM_USE_MPI
}
ParticleSet::ParticleSet(int id_stride_, IDType id_counter_, int num_particles,
int dim, Ordering::Type coords_ordering, const Array<int> &field_vdims,
const Array<Ordering::Type> &field_orderings,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_)
: id_stride(id_stride_),
id_counter(id_counter_),
coords(dim, coords_ordering)
{
// Initialize fields
for (int f = 0; f < field_vdims.Size(); f++)
{
AddField(field_vdims[f], field_orderings[f], field_names_[f]);
}
// Initialize tags
for (int t = 0; t < num_tags; t++)
{
AddTag(tag_names_[t]);
}
// Add num_particles
Array<IDType> init_ids(num_particles);
for (int i = 0; i < num_particles; i++)
{
init_ids[i] = id_counter;
id_counter += id_stride;
}
AddParticles(init_ids);
}
bool ParticleSet::IsValidParticle(const Particle &p) const
{
if (p.GetDim() != GetDim())
{
return false;
}
if (p.GetNFields() != GetNFields())
{
return false;
}
for (int f = 0; f < GetNFields(); f++)
{
if (p.GetFieldVDim(f) != Field(f).GetVDim())
{
return false;
}
}
if (p.GetNTags() != GetNTags())
{
return false;
}
return true;
}
ParticleSet::ParticleSet(int num_particles, int dim,
Ordering::Type coords_ordering)
: ParticleSet(1, 0, num_particles, dim, coords_ordering, Array<int>(),
Array<Ordering::Type>(), Array<const char*>(), 0,
Array<const char*>())
{
}
ParticleSet::ParticleSet(int num_particles, int dim,
const Array<int> &field_vdims, int num_tags,
Ordering::Type all_ordering)
: ParticleSet(1, 0, num_particles, dim, all_ordering, field_vdims,
GetOrderingArray(all_ordering, field_vdims.Size()),
GetEmptyNameArray(field_vdims.Size()), num_tags,
GetEmptyNameArray(num_tags))
{
}
ParticleSet::ParticleSet(int num_particles, int dim,
const Array<int> &field_vdims, const Array<const
char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_,
Ordering::Type all_ordering)
: ParticleSet(1, 0, num_particles, dim, all_ordering, field_vdims,
GetOrderingArray(all_ordering, field_vdims.Size()),
field_names_, num_tags,
tag_names_)
{
}
ParticleSet::ParticleSet(int num_particles, int dim,
Ordering::Type coords_ordering,
const Array<int> &field_vdims,
const Array<Ordering::Type> &field_orderings,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_)
: ParticleSet(1, 0, num_particles, dim, coords_ordering, field_vdims,
field_orderings, field_names_, num_tags, tag_names_)
{
}
#ifdef MFEM_USE_MPI
ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
Ordering::Type coords_ordering)
: ParticleSet(comm_, rank_num_particles, dim, coords_ordering, Array<int>(),
Array<Ordering::Type>(), Array<const char*>(), 0,
Array<const char*>())
{
};
ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
const Array<int> &field_vdims, int num_tags,
Ordering::Type all_ordering)
: ParticleSet(comm_, rank_num_particles, dim, all_ordering, field_vdims,
GetOrderingArray(all_ordering, field_vdims.Size()),
GetEmptyNameArray(field_vdims.Size()), num_tags,
GetEmptyNameArray(num_tags))
{
}
ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
const Array<int> &field_vdims, const Array<const
char*> &field_names_,
int num_tags, const Array<const char*> &tag_names_,
Ordering::Type all_ordering)
: ParticleSet(comm_, rank_num_particles, dim, all_ordering, field_vdims,
GetOrderingArray(all_ordering, field_vdims.Size()),
field_names_, num_tags,
tag_names_)
{
}
ParticleSet::ParticleSet(MPI_Comm comm_, int rank_num_particles, int dim,
Ordering::Type coords_ordering,
const Array<int> &field_vdims,
const Array<Ordering::Type> &field_orderings,
const Array<const char*> &field_names_, int num_tags,
const Array<const char*> &tag_names_)
: ParticleSet(GetSize(comm_), (IDType)GetRank(comm_),
rank_num_particles,
dim,
coords_ordering,
field_vdims,
field_orderings,
field_names_,
num_tags,
tag_names_)
{
comm = comm_;
#ifdef MFEM_USE_GSLIB
gsl_comm = new gslib::comm;
cr = new gslib::crystal;
comm_init(gsl_comm, comm);
crystal_init(cr, gsl_comm);
#endif // MFEM_USE_GSLIB
}
#endif // MFEM_USE_MPI
ParticleSet::IDType ParticleSet::GetGlobalNParticles() const
{
IDType total = (IDType)GetNParticles();
#ifdef MFEM_USE_MPI
MPI_Allreduce(MPI_IN_PLACE, &total, 1, MPI_UNSIGNED_LONG_LONG,
MPI_SUM, comm);
#endif // MFEM_USE_MPI
return total;
}
int ParticleSet::AddField(int vdim, Ordering::Type field_ordering,
const char* field_name)
{
std::string field_name_str(field_name ? field_name : "");
if (!field_name)
{
field_name_str = GetDefaultFieldName(field_names.size());
}
fields.emplace_back(std::make_unique<ParticleVector>(vdim, field_ordering,
GetNParticles()));
field_names.emplace_back(field_name_str);
return GetNFields() - 1;
}
int ParticleSet::AddTag(const char* tag_name)
{
std::string tag_name_str(tag_name ? tag_name : "");
if (!tag_name)
{
tag_name_str = GetDefaultTagName(tag_names.size());
}
tags.emplace_back(std::make_unique<Array<int>>(GetNParticles()));
tag_names.emplace_back(tag_name_str);
return GetNTags() - 1;
}
void ParticleSet::AddParticle(const Particle &p)
{
MFEM_ASSERT(IsValidParticle(p),
"Particle is incompatible with ParticleSet.");
// Add the particle
Array<int> idxs;
AddParticles(Array<IDType>({id_counter}), &idxs);
id_counter += id_stride;
// Set the new particle data
int idx = idxs[0];
SetParticle(idx, p);
}
void ParticleSet::AddParticles(int num_particles, Array<int> *new_indices)
{
Array<IDType> add_ids(num_particles);
for (int i = 0; i < num_particles; i++)
{
add_ids[i] = id_counter;
id_counter += id_stride;
}
AddParticles(add_ids, new_indices);
}
void ParticleSet::RemoveParticles(const Array<int> &list)
{
// Delete IDs
ids.DeleteAt(list);
// Delete data
for (int f = -1; f < GetNFields(); f++)
{
ParticleVector &pv = (f == -1 ? coords : *fields[f]);
pv.DeleteParticles(list);
}
// Delete tags
for (int t = 0; t < GetNTags(); t++)
{
tags[t]->DeleteAt(list);
}
}
Particle ParticleSet::GetParticle(int i) const
{
Particle p = CreateParticle();
Coords().GetValues(i, p.Coords());
for (int f = 0; f < GetNFields(); f++)
{
Field(f).GetValues(i, p.Field(f));
}
for (int t = 0; t < GetNTags(); t++)
{
p.Tag(t) = Tag(t)[i];
}
return p;
}
bool ParticleSet::IsParticleRefValid() const
{
if (coords.GetOrdering() == Ordering::byNODES)
{
return false;
}
for (int f = 0; f < GetNFields(); f++)
{
if (fields[f]->GetOrdering() == Ordering::byNODES)
{
return false;
}
}
return true;
}
Particle ParticleSet::GetParticleRef(int i)
{
Particle p = CreateParticle();
Coords().GetValuesRef(i, p.Coords());
for (int f = 0; f < GetNFields(); f++)
{
MFEM_ASSERT(Field(f).GetOrdering() == Ordering::byVDIM,
"GetParticleRef only valid when all fields ordered byVDIM.");
p.SetFieldRef(f, Field(f).GetData() + i*Field(f).GetVDim());
}
for (int t = 0; t < GetNTags(); t++)
{
p.SetTagRef(t, &(*tags[t])[i]);
}
return p;
}
void ParticleSet::SetParticle(int i, const Particle &p)
{
MFEM_ASSERT(IsValidParticle(p),
"Particle is incompatible with ParticleSet.");
Coords().SetValues(i, p.Coords());
for (int f = 0; f < GetNFields(); f++)
{
Field(f).SetValues(i, p.Field(f));
}
for (int t = 0; t < GetNTags(); t++)
{
Tag(t)[i] = p.Tag(t);
}
}
void ParticleSet::PrintCSV(const char *fname, int precision)
{
Array<int> all_field_idxs(GetNFields()), all_tag_idxs(GetNTags());
for (int f = 0; f < GetNFields(); f++)
{
all_field_idxs[f] = f;
}
for (int t = 0; t < GetNTags(); t++)
{
all_tag_idxs[t] = t;
}
PrintCSV(fname, all_field_idxs, all_tag_idxs, precision);
}
void ParticleSet::PrintCSV(const char *fname, const Array<int> &field_idxs,
const Array<int> &tag_idxs, int precision)
{
std::stringstream ss_header;
// Configure header:
ss_header << "id";
#ifdef MFEM_USE_MPI
ss_header << ",rank";
#endif // MFEM_USE_MPI
std::array<char, 3> ax = {'X', 'Y', 'Z'};
for (int c = 0; c < coords.GetVDim(); c++)
{
ss_header << "," << ax[c];
}
for (int f = 0; f < field_idxs.Size(); f++)
{
ParticleVector &pv = *fields[field_idxs[f]];
for (int c = 0; c < pv.GetVDim(); c++)
{
ss_header << "," << field_names[field_idxs[f]] <<
(pv.GetVDim() > 1 ? "_" + std::to_string(c) : "");
}
}
for (int t = 0; t < tag_idxs.Size(); t++)
{
ss_header << "," << tag_names[tag_idxs[t]];
}
ss_header << "\n";
// Configure data
std::stringstream ss_data;
ss_data.precision(precision);
#ifdef MFEM_USE_MPI
int rank = GetRank(comm);
#endif // MFEM_USE_MPI
for (int i = 0; i < GetNParticles(); i++)
{
ss_data << ids[i];
#ifdef MFEM_USE_MPI
ss_data << "," << rank;
#endif // MFEM_USE_MPI
for (int c = 0; c < coords.GetVDim(); c++)
{
ss_data << "," << coords(i, c);
}
for (int f = 0; f < field_idxs.Size(); f++)
{
ParticleVector &pv = *fields[field_idxs[f]];
for (int c = 0; c < pv.GetVDim(); c++)
{
ss_data << "," << pv(i, c);
}
}
for (int t = 0; t < tag_idxs.Size(); t++)
{
ss_data << "," << (*tags[tag_idxs[t]])[i];
}
ss_data << "\n";
}
// Write
WriteToFile(fname, ss_header, ss_data);
}
ParticleSet::~ParticleSet()
{
#if defined(MFEM_USE_MPI) && defined(MFEM_USE_GSLIB)
if (gsl_comm)
{
if (!Mpi::IsFinalized()) // currently segfaults inside gslib otherwise
{
crystal_free(cr);
comm_free(gsl_comm);
delete gsl_comm;
delete cr;
}
}
#endif // MFEM_USE_MPI && MFEM_USE_GSLIB
}
} // namespace mfem