8#ifndef GRIDFORMAT_VTK_PARALLEL_HPP_
9#define GRIDFORMAT_VTK_PARALLEL_HPP_
22#include <gridformat/common/math.hpp>
23#include <gridformat/common/exceptions.hpp>
24#include <gridformat/common/concepts.hpp>
25#include <gridformat/common/ranges.hpp>
27#include <gridformat/grid/grid.hpp>
29#include <gridformat/parallel/communication.hpp>
30#include <gridformat/parallel/helpers.hpp>
32#include <gridformat/xml/element.hpp>
35namespace GridFormat::PVTK {
39 const std::string base_name = par_filename.substr(0, par_filename.find_last_of(
"."));
40 return base_name +
"-" + std::to_string(rank);
44template<
typename Encoder,
typename DataFormat>
55 void add(
const std::string& name,
const Field& field) {
58 static constexpr std::size_t vtk_space_dim = 3;
59 const auto layout = field.
layout();
60 std::size_t ncomps = std::pow(vtk_space_dim, field.
layout().dimension() - 1);
63 if (layout.dimension() == 2 && layout.extent(1) > vtk_space_dim)
64 ncomps = layout.extent(1);
66 XMLElement& arr = _element.add_child(
"PDataArray");
67 arr.set_attribute(
"Name", name);
68 arr.set_attribute(
"type", VTK::attribute_name(field.
precision()));
69 arr.set_attribute(
"format", VTK::data_format_name(_encoder, _data_format));
70 arr.set_attribute(
"NumberOfComponents", ncomps);
74 const Encoder& _encoder;
75 const DataFormat& _data_format;
79template<std::
size_t dim>
82 using Location = std::array<std::size_t, dim>;
85 : _map{std::move(map)}
88 const auto& location(std::integral
auto rank)
const {
92 std::ranges::view
auto ranks_below(
const Location& loc,
unsigned direction)
const {
93 return std::views::filter(
94 std::views::iota(std::size_t{0}, _map.size()),
95 [&, loc=loc, direction=direction] (
const std::size_t rank) {
96 const auto& rank_loc = _map[rank];
97 for (
unsigned dir = 0; dir < dim; ++dir)
98 if (dir != direction && rank_loc[dir] != loc[dir]) {
101 return rank_loc[direction] < loc[direction];
107 std::vector<Location> _map;
112template<
typename T, std::
size_t dim>
115 using Origin = std::array<T, dim>;
116 using Basis = std::array<Origin, dim>;
120 T default_epsilon = 1e-6)
124 , _default_epsilon{default_epsilon} {
125 std::ranges::fill(_reverse,
false);
128 void reverse(
int direction) {
129 _reverse[direction] = !_reverse[direction];
132 void set_origin_for(
int rank, Origin origin) {
134 throw ValueError(
"Origin for given rank already set");
135 _origins[rank] = std::move(origin);
139 auto make_mapper()
const {
140 if (_origins.empty())
141 throw InvalidState(
"No origins have been set");
143 std::vector<std::array<std::size_t, dim>> map(_origins.size());
144 for (
unsigned dir = 0; dir < dim; ++dir) {
145 auto ordinates = _get_ordinates(dir);
146 const auto eps = _epsilon(ordinates);
147 _sort_ordinates(ordinates, eps, _reverse[dir]);
149 if (ordinates.size() > 1) {
150 for (
unsigned rank = 0; rank < _origins.size(); ++rank) {
151 const auto rank_origin_ordinate = _get_ordinate(_origins[rank], dir);
152 auto it = std::ranges::find_if(ordinates, [&] (
const T o) {
154 return abs(o - rank_origin_ordinate) < eps;
156 if (it == ordinates.end())
157 throw InvalidState(
"Could not determine rank ordinate");
158 map[rank][dir] = std::distance(ordinates.begin(), it);
161 for (
unsigned rank = 0; rank < _origins.size(); ++rank)
168 Origin compute_origin()
const {
169 if (_origins.empty())
170 throw InvalidState(
"No origins have been set");
173 for (
unsigned dir = 0; dir < dim; ++dir) {
174 auto ordinates = _get_ordinates(dir);
175 _sort_ordinates(ordinates, _reverse[dir]);
176 result[dir] = ordinates[0];
182 auto _get_ordinates(
unsigned int axis)
const {
183 std::vector<T> result;
184 result.reserve(_origins.size());
186 std::views::transform(_origins, [&] (
const Origin& o) {
187 return _get_ordinate(o, axis);
189 std::back_inserter(result)
194 auto _get_ordinate(
const Origin& o,
unsigned int axis)
const {
195 return dot_product(o, _basis[axis]);
198 void _sort_ordinates(std::vector<T>& ordinates,
bool reverse)
const {
199 if (ordinates.size() > 1)
200 _sort_ordinates(ordinates, _epsilon(ordinates), reverse);
203 void _sort_ordinates(std::vector<T>& ordinates,
const T& eps,
bool reverse)
const {
204 if (ordinates.size() > 1) {
205 auto [it, _] = Ranges::sort_and_unique(ordinates, {}, [&] (T a, T b) {
207 return abs(a - b) < eps;
209 ordinates.erase(it, ordinates.end());
211 std::ranges::reverse(ordinates);
215 T _epsilon(
const std::vector<T>& ordinates)
const {
216 const auto max = *std::ranges::max_element(ordinates);
217 const auto min = *std::ranges::min_element(ordinates);
218 const auto size = max - min;
220 if (size > _default_epsilon) {
221 std::vector<T> dx; dx.reserve(ordinates.size());
222 std::adjacent_difference(ordinates.begin(), ordinates.end(), std::back_inserter(dx));
223 std::ranges::for_each(dx, [] (
auto& v) { v = std::abs(v); });
224 std::ranges::sort(dx);
230 return _default_epsilon;
234 std::vector<Origin> _origins;
235 std::vector<bool> _set;
236 std::array<bool, dim> _reverse;
241template<
typename Communicator>
246 , _root_rank{root_rank}
249 template<Concepts::Scalar CT,
251 Concepts::StaticallySizedMDRange<2> B = std::array<std::array<CT, dim>, dim>>
252 auto compute_extents_and_origin(
const std::vector<CT>& all_origins,
253 const std::vector<std::size_t>& all_extents,
254 const std::array<bool, dim>& is_negative_axis,
255 const B& basis = GridDetail::standard_basis<CT, dim>())
const {
256 const auto num_ranks = Parallel::size(_comm);
257 std::vector<std::array<std::size_t, dim>> pieces_begin(num_ranks);
258 std::vector<std::array<std::size_t, dim>> pieces_end(num_ranks);
259 std::array<std::size_t, dim> whole_extent;
260 std::array<CT, dim> origin;
262 if (Parallel::rank(_comm) == _root_rank) {
263 const auto default_epsilon = 1e-6*std::ranges::max(all_origins | std::views::transform([] (CT v) {
267 const auto mapper_helper = _make_mapper_helper(basis, all_origins, is_negative_axis, default_epsilon);
268 const auto rank_mapper = mapper_helper.make_mapper();
269 origin = mapper_helper.compute_origin();
271 for (
unsigned dir = 0; dir < dim; ++dir) {
272 for (
int rank = 0; rank < num_ranks; ++rank) {
273 auto ranks_below = rank_mapper.ranks_below(rank_mapper.location(rank), dir);
274 const std::size_t offset = std::accumulate(
275 std::ranges::begin(ranks_below),
276 std::ranges::end(ranks_below),
278 [&] (
const std::size_t current,
int r) {
279 return current + Parallel::access_gathered<dim>(all_extents, _comm, {dir, r});
282 pieces_begin[rank][dir] = offset;
283 pieces_end[rank][dir] = offset + Parallel::access_gathered<dim>(all_extents, _comm, {dir, rank});
286 whole_extent[dir] = (*std::max_element(
287 pieces_end.begin(), pieces_end.end(),
288 [&] (
const auto& a1,
const auto& a2) { return a1[dir] < a2[dir]; }
293 return std::make_tuple(
294 std::move(pieces_begin),
295 std::move(pieces_end),
296 std::move(whole_extent),
302 template<Concepts::StaticallySizedMDRange<2> Basis, Concepts::Scalar CT, std::
size_t dim>
303 auto _make_mapper_helper(
const Basis& basis,
304 const std::vector<CT>& all_origins,
305 const std::array<bool, dim>& is_negative_axis,
306 CT default_eps)
const {
308 std::ranges::for_each(Parallel::ranks(_comm), [&] (
int rank) {
309 helper.set_origin_for(rank, Parallel::access_gathered<dim>(all_origins, _comm, rank));
312 for (
unsigned dir = 0; dir < dim; ++dir)
313 if (is_negative_axis[dir])
319 const Communicator& _comm;
Helper functions to get the VTK-specific names of things.
std::string piece_basefilename(const std::string &par_filename, int rank)
Return the piece filename (w/o extension) for the given rank.
Definition: parallel.hpp:38