Commit 049419c1 authored by Quentin Khan's avatar Quentin Khan

Add CPP-Utils utilities to contribs

parent d9de96a4
This diff is collapsed.
#ifndef _BINARY_SEARCH_HPP_
#define _BINARY_SEARCH_HPP_
#include <functional>
namespace inria {
namespace details {
template<class Compare = std::less<void>, class T1, class T2>
bool less(const T1& lhs, const T2& rhs, Compare comp) {
return comp(lhs, rhs);
}
template<class Compare, class T1, class T2>
bool greater(const T1& lhs, const T2& rhs, Compare comp) {
return less(rhs, lhs, comp);
}
template<class Compare, class T1, class T2>
bool less_equal(const T1& lhs, const T2& rhs, Compare comp) {
return !greater(lhs,rhs, comp);
}
template<class Compare, class T1, class T2>
bool greater_equal(const T1& lhs, const T2& rhs, Compare comp) {
return !less(lhs, rhs, comp);
}
template<class Compare, class T1, class T2>
bool equal(const T1& lhs, const T2& rhs, Compare comp) {
return !less(lhs,rhs,comp) && !greater(lhs,rhs,comp);
}
template<class Compare, class T1, class T2>
bool different(const T1& lhs, const T2& rhs, Compare comp) {
return less(lhs,rhs,comp) || greater(lhs,rhs,comp);
}
}
namespace tag {
/// Find first element equal to target or the first element greater than target
/// in the range.
struct first_or_next {
template<class Compare, class T1, class T2>
static constexpr bool compare(const T1& lhs, const T2& rhs, Compare comp) {
return details::less_equal(lhs, rhs, comp);
}
};
/// Find element right after the target in the range.
struct next {
template<class Compare, class T1, class T2>
static constexpr bool compare(const T1& lhs, const T2& rhs, Compare comp) {
return details::less(lhs, rhs, comp);
}
};
} // namespace tag
/**
* \brief Runs a binary search
*
* Performs a binary search over the sorted range \[first, last\) for the target
* value. The ComparePolicy determines the returned iterator:
* - inria::tag::first_or_next, if the value is found, its first position is
* returned, ortherwise the position of the next value is returned;
* - inria::tag::next, the position of the next value is always returned.
*
* \param first Iterator to the first element of the range.
* \param last Iterator to the last element of the range.
* \param target Value to look for.
*
* \tparam ComparePolicy The comparison policy, either inria::tag::first_or_next
* or inria::tag::next
* \tparam RandomAccessIterator Iterator over a range of elements.
* \tparam T Comparison target type
* \tparam Compare Comparison functor type. Must expose an operator() with the
* signature `bool operator()(T target, U elem)` which returns
* `true` if `target < elem`.
*
* \return The position of the first element to match the target. If no element
* matches, the position of the first element greater to target is returned
* (this may be the range past-the-end iterator).
*
* \warning \[first, last\) must be sorted.
*
* \note It came to my attention to the STL already implements this function (or
* very close to) with the algorithms std::lower_bound and std::upper_bound.
*
* Example:
*
* ~~~{.cpp}
* std::array<int, 20> data {};
* int tmp = 0;
* std::generate(begin(data), end(data), []{return tmp += 2;});
*
* binary_search();
* ~~~
*/
template<class ComparePolicy, class RandomAccessIterator, class T, class Compare = std::less<T>>
[[gnu::deprecated("Use std::lower_bound (equivalent to tag::first_or_next) or std::upper_bound (equivalent to tag::next) instead")]]
RandomAccessIterator binary_search(
ComparePolicy,
RandomAccessIterator first,
RandomAccessIterator last,
const T& target,
Compare comp = {})
{
static_assert(std::is_same<ComparePolicy, tag::first_or_next>::value
|| std::is_same<ComparePolicy, tag::next>::value,
"The ComparisonPolicy type must be one of "
"inria::tag::first_or_next, inria::tag::next");
while(last - first > 2) {
RandomAccessIterator median = first + ((last - first) / 2);
if(ComparePolicy::compare(target, *median, comp)) {
last = median + 1;
} else {
first = median;
}
}
if(first < last && ComparePolicy::compare(target, *first, comp)) {
return first;
} else if(last - first == 2 && ComparePolicy::compare(target, *(first+1), comp)) {
return first+1;
} else {
return last;
}
}
}
#endif /* _BINARY_SEARCH_HPP_ */
This diff is collapsed.
This diff is collapsed.
#ifndef _REMOVE_DUPLICATES_HPP_
#define _REMOVE_DUPLICATES_HPP_
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <type_traits>
#include "mpi.hpp"
#include "inria/utils.hpp"
namespace inria {
/**
* \brief Remove duplicates in a distributed sorted range
*
* The elements held localy are in the range [first, last[.
*
* \note The order the elements are sorted in is not important. Equal elements
* must be consecutive, possibly accros process boundaries.
*
* \param first Iterator to the range first element.
* \param last Iterator past the range last element.
* \param comp Function object that returns true when two elements are equal.
* \param mpi_type MPI datatype corresponding to the elements in the range.
* \param comm MPI communicator.
*
* \tparam It Bidirectional iterator
* \tparam Compare Comparison type. Must expose a call operator such as
* `Compare::operator(T a, T b)` returns true if `a == b` and
* false otherwise.
* \tparam T The type of the range elements. T must be trivially copyable.
*
* \return The new end of the range. The elements past this new are are in an
* unspecified state as per move operator.
*
* \warning The duplicate elements are not actually removed. It is the
* programmer's reponsibility to resize the actual range if needed.
*/
template<class It, class Compare>
It unique(mpi_config conf, It first, It last, Compare comp) {
using T = typename std::iterator_traits<It>::value_type;
enum {NONE_TAG = 65, OK_TAG = 94};
static_assert(std::is_trivially_copyable<T>::value,
"The range elements must be trivially copiable");
// Guess datatype if it is MPI_DATATYPE_NULL
auto type_guard = mpi::create_datatype_if_null<T>(conf.datatype);
const int proc_count = conf.comm.size();
const int rank = conf.comm.rank();
T prev_last_elem;
if(proc_count > 1) {
if(rank == 0) {
if(last - first == 0) {
conf.comm.send(nullptr, 0, MPI_BYTE, 1, conf.base_tag + NONE_TAG);
} else {
T last_elem = *(last-1);
conf.comm.send(&last_elem, 1, conf.datatype, rank+1,
conf.base_tag + OK_TAG);
}
} else {
mpi::status stat;
conf.comm.recv(&prev_last_elem, 1, conf.datatype, rank-1, MPI_ANY_TAG, stat);
if(rank < proc_count -1) {
if(last - first == 0) {
conf.comm.send(&prev_last_elem, 1, conf.datatype, rank+1, stat.raw_status.MPI_TAG);
} else {
T last_elem = *(last-1);
conf.comm.send(&last_elem, 1, conf.datatype, rank+1, conf.base_tag + OK_TAG);
}
}
// If the previous process sent an element prev_last_elem, mark it
// for removal by ovewriting it whith an other element from the
// range. The duplicates will then be removed using a local unique
// algorithm.
//
// The element copied is the first element different from the
// received one.
//
// This avoid shifting the whole range to remove the previous
// process element.
if(stat.raw_status.MPI_TAG != conf.base_tag + NONE_TAG) {
auto not_prev_last_elem = [&comp, &prev_last_elem](const T& e) {
return !comp(e, prev_last_elem);
};
auto first_not_prev_last_elem = std::find_if(first, last, not_prev_last_elem);
if(first_not_prev_last_elem != last) {
std::fill(first, first_not_prev_last_elem, *first_not_prev_last_elem);
} else {
// all elements are equal to the previous process last element
last = first;
}
}
}
}
return std::unique(first, last, comp);
}
template<class It>
It unique(mpi_config conf, It first, It last) {
using T = typename std::iterator_traits<It>::value_type;
return unique(conf, first, last, std::equal_to<T>{});
}
/**
* \brief Erases duplicates in a container and resizes it accordingly.
*/
template<class Container, class Compare>
void unique(mpi_config conf, Container& container, Compare comp) {
auto new_end = unique(conf, std::begin(container), std::end(container), comp);
container.erase(new_end, std::end(container));
}
template<class Container>
void unique(mpi_config conf, Container& container) {
auto new_end = unique(conf, std::begin(container), std::end(container));
container.erase(new_end, std::end(container));
}
}
#endif /* _REMOVE_DUPLICATES_HPP_ */
#ifndef _CHECKER_HPP_
#define _CHECKER_HPP_
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include <tuple>
namespace inria {
struct locus_t {
int line;
std::string file;
friend std::ostream& operator<<(std::ostream& os, const locus_t& l) {
return os << l.file << ':' << l.line;
}
friend locus_t operator+(const locus_t& l, int offset) {
return {l.line + offset, l.file};
}
};
#define LOCUS ::inria::locus_t{__LINE__,__FILE__}
class checker_t {
template<class T>
struct has_formated_output {
template<class U>
static constexpr auto check(std::ostream* os, U* o)
-> decltype((*os << *o), std::true_type{} ) ;
static constexpr auto check(...)
-> std::false_type;
enum {value = decltype(check(nullptr, (T*)0))::value};
};
struct res_t {
bool val;
locus_t locus;
operator bool() const noexcept {return val;}
};
bool silence_;
std::vector<res_t> results;
template<class T, class = typename std::enable_if<has_formated_output<T>::value>::type>
const T& get_print_val(const T& t) {
return t;
}
template<class T, class = typename std::enable_if<! has_formated_output<T>::value>::type>
std::string get_print_val(const T&) {
return "<non-printable object>";
}
template<class T, class U>
bool check_res(bool res, std::string desc, const T& value, const U& target, locus_t locus) {
results.emplace_back(res_t{res, locus});
if(! results.back()) {
if(locus.line) {
std::cerr << locus << ": ";
}
std::cerr << "Check failed (id: " << results.size() << "): "
<< get_print_val(value)
<< " <" << desc << "> "
<< get_print_val(target)
<< '\n';
}
return results.back();
}
public:
checker_t() = default;
checker_t(const checker_t&) = default;
checker_t(checker_t&&) = default;
checker_t& operator=(const checker_t&) = default;
checker_t& operator=(checker_t&&) = default;
checker_t(bool silence) : silence_(silence) {}
bool silence() const {
return silence_;
}
bool silence(bool value) {
return silence_ = value;
}
bool fail(const std::string& msg, locus_t locus) {
return check_res(false, msg, "", "", locus);
}
bool succeed(locus_t locus) {
return check_res(true, "", "", "", locus);
}
template<class T>
bool is_true(const T& value, locus_t locus) {
return equal(value, true, locus);
}
template<class T>
bool is_false(const T& value, locus_t locus) {
return equal(value, false, locus);
}
template<class T, class U>
bool equal(const T& value, const U& target, locus_t locus) {
return check_res(value == target, "equal", value, target, locus);
}
template<class T, class U>
bool different(const T& value, const U& target, locus_t locus) {
return check_res(value != target, "different", value, target, locus);
}
template<class T, class U>
bool less_eq(const T& value, const U& target, locus_t locus) {
return check_res(value <= target, "less_eq", value, target, locus);
}
template<class T, class U>
bool less(const T& value, const U& target, locus_t locus) {
return check_res(value < target, "less", value, target, locus);
}
template<class T, class U>
bool greater(const T& value, const U& target, locus_t locus) {
return check_res(value > target, "greater", value, target, locus);
}
template<class T, class U>
bool greater_eq(const T& value, const U& target, locus_t locus) {
return check_res(value >= target, "greater_eq", value, target, locus);
}
std::pair<std::size_t, std::size_t> summary() const {
return {std::count(std::begin(results),std::end(results),true),
results.size()};
}
bool ok() const {
return std::all_of(std::begin(results), std::end(results), [](char c) {return c;});
};
void print_summary() {
std::size_t passed, tried;
std::tie(passed, tried) = summary();
std::size_t failed = tried - passed;
if(passed == tried) {
std::cout << "Tests PASSED (" << passed << ")\n";
} else {
std::cout << failed << '/' << tried << " test" << (failed > 1 ? "s" : "") << " failed.\n";
}
}
~checker_t() {
if(!this->silence() && results.size() != 0) {
print_summary();
}
}
};
} // end namespace inria
#endif /* _CHECKER_HPP_ */
This diff is collapsed.
#ifndef _IO_HPP_
#define _IO_HPP_
#include <ostream>
#include <cassert>
namespace inria {
namespace io {
namespace details {
/**
* \brief Print configuration
*/
template<class Separator, class Open, class Close>
struct io_range_data {
const Separator& sep;
const Open& open;
const Close& close;
// Clang segfaults if the constructor is not defined
io_range_data(const Separator& s, const Open& o, const Close& c) :
sep(s), open(o), close(c) {}
};
template<class C, class Separator, class Open, class Close,
bool is_const, bool is_rvalue_ref>
struct io_range_t;
/**
* \brief Keep a reference to range container
*/
template<class C, class Separator, class Open, class Close>
struct io_range_t<C, Separator, Open, Close, false, false> {
C& c;
io_range_data<Separator, Open, Close> data;
enum {reference_range};
};
/**
* \brief Keep a const reference to range container
*/
template<class C, class Separator, class Open, class Close>
struct io_range_t<C, Separator, Open, Close, true, false> {
const C& c;
io_range_data<Separator, Open, Close> data;
enum {const_reference_range};
};
/**
* \brief Store the container when printing an rvalue container
*/
template<class C, class Separator, class Open, class Close, bool B>
struct io_range_t<C, Separator, Open, Close, B, true> {
C c;
io_range_data<Separator, Open, Close> data;
enum {value_range};
};
/**
* \brief Formatted range output operator
*/
template<class C, class Separator, class Open, class Close, bool B, bool B2>
std::ostream& operator<<(std::ostream& os,
const io_range_t<C, Separator, Open, Close, B, B2>& c)
{
auto b = std::begin(c.c);
const auto e = std::end(c.c);
os << c.data.open;
if(!(b == e)) {
os << *b;
++b;
}
while(!(b == e)) {
os << c.data.sep;
os << *b;
++b;
}
os << c.data.close;
return os;
}
} // end namespace details
/**
* \brief Create an object from a range that can be printed
*/
template<class C, class Separator = const char*, class Open = const char*, class Close = const char*>
auto range(C&& c, const Separator& sep = ", ", const Open& open = "", const Close& close = "")
-> details::io_range_t<
C, Separator, Open, Close,
std::is_const<typename std::remove_reference<C>::type>::value,
std::is_rvalue_reference<decltype(std::forward<C>(c))>::value>
{
return details::io_range_t<
C, Separator, Open, Close,
std::is_const<typename std::remove_reference<C>::type>::value,
std::is_rvalue_reference<decltype(std::forward<C>(c))>::value>
{std::forward<C>(c), {{sep}, {open}, {close}}};
}
}} // end namespace inria[::io]
#endif /* _IO_HPP_ */
#ifndef _INRIA_BALANCE_TREE_
#define _INRIA_BALANCE_TREE_
#include "weight_traits.hpp"
#include "distributed_regions_to_linear_tree.hpp"
#include "gather_octant_weights.hpp"
#include "inria/algorithm/distributed/sort.hpp"
#include "inria/algorithm/distributed/distribute.hpp"
namespace inria {
namespace linear_tree {
namespace details {
/**
* \brief Node info with added weigh information
*/
template<class P>
struct weighted_node_info : node::info<P::position_t::Dim> {
/// Weight attribute type
using weight_t = decltype(inria::meta::get_weight(std::declval<P>()));
/// Node weight
weight_t weight = 0;
// Use parent constructor
using node::info<P::position_t::Dim>::info;
/**
* \brief Output stream operator
*
* \param os Output stream
* \param n Weighted node info object to stream
*/
friend std::ostream& operator<<(std::ostream& os, const weighted_node_info& n) {
using node::level;
using node::morton_index;
return os << '(' << morton_index(n) << ", " << level(n) << ", " << n.weight << ')';
}
};
/**
* \brief Function object to access weight
*/
struct weight_accessor {
template<class T>
auto operator()(const T& e) const noexcept(noexcept(inria::meta::get_weight(e)))
-> decltype(inria::meta::get_weight(e))
{
return inria::meta::get_weight(e);
}
};
/**
* \brief Helper type aliases for create_balanced_linear_tree
*/
namespace cblt {
/// Alias to extract the node::info<Dim> type from a particle iterator
template<class T>
using node_info_from_it = node::info<std::iterator_traits<T>::value_type::position_t::Dim>;
/// Alias to extract the node::info<Dim> type from a particle range
template<class T>
using node_info_from_range = node::info<T::value_type::position_t::Dim>;
} // close namespace [inria::linear_tree::details]::cblts
} // close namespace [inria::linear_tree]::details
/**
* \brief Create a distributed linear tree from a sorted particle list
*
* \warning The particle list must be sorted accross the processes.
*
* \param conf MPI configuration
* \param level_ Maximum tree depth
* \param box The space bounding box
* \param sorted_particles Distributed list of particles sorted by morton index
*
* \note The is box expected to be a cube.
*
* \tparam Range Particle list, must define begin and end functions and a
* value_type type alias.
* \tparam Box Must define `Box::width(int axis)` and `Box::corner(int morton_index)`.
*
* \return A holding the local leaf information (inria::linear_tree::node::info)
* of the tree.
*/
template<class Range, class Box>
std::vector<details::cblt::node_info_from_range<Range>>
create_balanced_linear_tree(
inria::mpi_config conf,
std::size_t level_,
Box box,
Range& sorted_particles)
{
using particle_t = typename Range::value_type;
using node_info_t = details::weighted_node_info<particle_t>;
// Compute minimal and maximal octant for local range.
node_info_t min_oct{}, max_oct{};
if(! sorted_particles.empty()) {
auto min_idx = get_morton_index(sorted_particles.front().position(), box, level_);
min_oct = node_info_t{min_idx, level_};
auto max_idx = get_morton_index(sorted_particles.back().position(), box, level_);
max_oct = node_info_t{max_idx, level_};
}
// Complete the local region based on sorted particles
std::vector<node_info_t> local_region;
complete_region(min_oct, max_oct, local_region);
// Keep only the coarsest octants
std::size_t min_level = std::accumulate(begin(local_region), end(local_region), level_,
[](std::size_t l, const node_info_t& n) {
return std::min(l, level(n));
});
local_region.erase(std::remove_if(begin(local_region), end(local_region),
[&](const node_info_t& n) {
return level(n) > min_level;
}),
end(local_region));
// Complete distributed octree
std::vector<node_info_t> local_tree;
distributed_regions_to_linear_tree(conf, begin(local_region), end(local_region), local_tree);
// Compute the weight of each octant to redistribute them among processes
gather_octants_weight(conf, begin(local_tree), end(local_tree),
begin(sorted_particles), end(sorted_particles), box);
distribute(conf, local_tree, uniform_distribution<details::weight_accessor>{conf, local_tree});
coarsen_region(local_tree);