piranha  0.10
kronecker_array.hpp
1 /* Copyright 2009-2017 Francesco Biscani (bluescarni@gmail.com)
2 
3 This file is part of the Piranha library.
4 
5 The Piranha library is free software; you can redistribute it and/or modify
6 it under the terms of either:
7 
8  * the GNU Lesser General Public License as published by the Free
9  Software Foundation; either version 3 of the License, or (at your
10  option) any later version.
11 
12 or
13 
14  * the GNU General Public License as published by the Free Software
15  Foundation; either version 3 of the License, or (at your option) any
16  later version.
17 
18 or both in parallel, as here.
19 
20 The Piranha library is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23 for more details.
24 
25 You should have received copies of the GNU General Public License and the
26 GNU Lesser General Public License along with the Piranha library. If not,
27 see https://www.gnu.org/licenses/. */
28 
29 #ifndef PIRANHA_KRONECKER_ARRAY_HPP
30 #define PIRANHA_KRONECKER_ARRAY_HPP
31 
32 #include <algorithm>
33 #include <cstddef>
34 #include <iterator>
35 #include <limits>
36 #include <numeric>
37 #include <random>
38 #include <stdexcept>
39 #include <tuple>
40 #include <type_traits>
41 #include <utility>
42 #include <vector>
43 
44 #include <piranha/config.hpp>
45 #include <piranha/exceptions.hpp>
46 #include <piranha/mp_integer.hpp>
47 #include <piranha/safe_cast.hpp>
48 #include <piranha/type_traits.hpp>
49 
50 namespace piranha
51 {
52 
53 namespace detail
54 {
55 
56 // Type requirement for Kronecker array.
57 template <typename T>
58 using ka_type_reqs = std::integral_constant<bool, std::is_integral<T>::value && std::is_signed<T>::value>;
59 }
60 
62 
84 // NOTE: we should optimise the decodification with only one element, there should be no need to do divisions
85 // and modulo operations.
86 template <typename SignedInteger>
88 {
89 public:
91  typedef SignedInteger int_type;
92 
93 private:
94 #if !defined(PIRANHA_DOXYGEN_INVOKED)
95  static_assert(detail::ka_type_reqs<int_type>::value, "This class can be used only with signed integers.");
96  // This is a 4-tuple of int_type built as follows:
97  // 0. vector of absolute values of the upper/lower limit for each component,
98  // 1. h_min,
99  // 2. h_max,
100  // 3. h_max - h_min.
101  typedef std::tuple<std::vector<int_type>, int_type, int_type, int_type> limit_type;
102  // Vector of limits.
103  typedef std::vector<limit_type> limits_type;
104 #endif
105 public:
107 
111  typedef std::size_t size_type;
112 
113 private:
114  // Static vector of limits built at startup.
115  // NOTE: here we should not have problems when interoperating with libraries that modify the GMP allocation
116  // functions,
117  // as we do not store any static piranha::integer: the creation and destruction of integer objects is confined to
118  // the determine_limit()
119  // function.
120  static const limits_type m_limits;
121  // Determine limits for m-dimensional vectors.
122  // NOTE: when reasoning about this, keep in mind that this is not a completely generic
123  // codification: min/max vectors are negative/positive and symmetric. This makes it easy
124  // to reason about overflows during (de)codification of vectors, representability of the
125  // quantities involved, etc.
126  static limit_type determine_limit(const size_type &m)
127  {
128  piranha_assert(m >= 1u);
129  std::mt19937 engine(static_cast<std::mt19937::result_type>(m));
130  std::uniform_int_distribution<int> dist(-5, 5);
131  // Perturb integer value: add random quantity and then take next prime.
132  auto perturb = [&engine, &dist](integer &arg) {
133  arg += (dist(engine) * (arg)) / 100;
134  arg.nextprime();
135  };
136  // Build initial minmax and coding vectors: all elements in the [-1,1] range.
137  std::vector<integer> m_vec, M_vec, c_vec, prev_c_vec, prev_m_vec, prev_M_vec;
138  c_vec.push_back(integer(1));
139  m_vec.push_back(integer(-1));
140  M_vec.push_back(integer(1));
141  for (size_type i = 1u; i < m; ++i) {
142  m_vec.push_back(integer(-1));
143  M_vec.push_back(integer(1));
144  c_vec.push_back(c_vec.back() * integer(3));
145  }
146  // Functor for scalar product of two vectors.
147  auto dot_prod = [](const std::vector<integer> &v1, const std::vector<integer> &v2) -> integer {
148  piranha_assert(v1.size() && v1.size() == v2.size());
149  return std::inner_product(v1.begin(), v1.end(), v2.begin(), integer(0));
150  };
151  while (true) {
152  // Compute the current h_min/max and diff.
153  integer h_min = dot_prod(c_vec, m_vec);
154  integer h_max = dot_prod(c_vec, M_vec);
155  integer diff = h_max - h_min;
156  piranha_assert(diff >= 0);
157  try {
158  // Try to cast everything to hardware integers.
159  (void)static_cast<int_type>(h_min);
160  (void)static_cast<int_type>(h_max);
161  // Here it is +1 because h_max - h_min must be strictly less than the maximum value
162  // of int_type. In the paper, in eq. (7), the Delta_i product appearing in the
163  // decoding of the last component of a vector is equal to (h_max - h_min + 1) so we need
164  // to be able to represent it.
165  (void)static_cast<int_type>(diff + 1);
166  // NOTE: we do not need to cast the individual elements of m/M vecs, as the representability
167  // of h_min/max ensures the representability of m/M as well.
168  } catch (const std::overflow_error &) {
169  std::vector<int_type> tmp;
170  // Check if we are at the first iteration.
171  if (prev_c_vec.size()) {
172  h_min = dot_prod(prev_c_vec, prev_m_vec);
173  h_max = dot_prod(prev_c_vec, prev_M_vec);
174  std::transform(prev_M_vec.begin(), prev_M_vec.end(), std::back_inserter(tmp),
175  [](const integer &n) { return static_cast<int_type>(n); });
176  return std::make_tuple(tmp, static_cast<int_type>(h_min), static_cast<int_type>(h_max),
177  static_cast<int_type>(h_max - h_min));
178  } else {
179  // Here it means m variables are too many, and we stopped at the first iteration
180  // of the cycle. Return tuple filled with zeroes.
181  return std::make_tuple(tmp, int_type(0), int_type(0), int_type(0));
182  }
183  }
184  // Store old vectors.
185  prev_c_vec = c_vec;
186  prev_m_vec = m_vec;
187  prev_M_vec = M_vec;
188  // Generate new coding vector for next iteration.
189  auto it = c_vec.begin() + 1, prev_it = prev_c_vec.begin();
190  for (; it != c_vec.end(); ++it, ++prev_it) {
191  // Recover original delta.
192  *it /= *prev_it;
193  // Multiply by two and perturb.
194  *it *= 2;
195  perturb(*it);
196  // Multiply by the new accumulated delta product.
197  *it *= *(it - 1);
198  }
199  // Fill in the minmax vectors, apart from the last component.
200  it = c_vec.begin() + 1;
201  piranha_assert(M_vec.size() && M_vec.size() == m_vec.size());
202  for (size_type i = 0u; i < M_vec.size() - 1u; ++i, ++it) {
203  M_vec[i] = ((*it) / *(it - 1) - 1) / 2;
204  m_vec[i] = -M_vec[i];
205  }
206  // We need to generate the last interval, which does not appear in the coding vector.
207  // Take the previous interval and enlarge it so that the corresponding delta is increased by a
208  // perturbed factor of 2.
209  M_vec.back() = (4 * M_vec.back() + 1) / 2;
210  perturb(M_vec.back());
211  m_vec.back() = -M_vec.back();
212  }
213  }
214  static limits_type determine_limits()
215  {
216  limits_type retval;
217  retval.emplace_back(std::vector<int_type>{}, int_type(0), int_type(0), int_type(0));
218  for (size_type i = 1u;; ++i) {
219  auto tmp = determine_limit(i);
220  if (std::get<0u>(tmp).empty()) {
221  break;
222  } else {
223  retval.push_back(std::move(tmp));
224  }
225  }
226  return retval;
227  }
228 
229 public:
231 
252  static const limits_type &get_limits()
253  {
254  return m_limits;
255  }
257 
275  template <typename Vector>
276  static int_type encode(const Vector &v)
277  {
278  const auto size = v.size();
279  // NOTE: here the check is >= because indices in the limits vector correspond to the sizes of the vectors to be
280  // encoded.
281  if (unlikely(size >= m_limits.size())) {
282  piranha_throw(std::invalid_argument, "size of vector to be encoded is too large");
283  }
284  if (unlikely(!size)) {
285  return int_type(0);
286  }
287  // Cache quantities.
288  const auto &limit = m_limits[size];
289  const auto &minmax_vec = std::get<0u>(limit);
290  // Check that the vector's components are compatible with the limits.
291  // NOTE: here size is not greater than m_limits.size(), which in turn is compatible with the minmax vectors.
292  for (min_int<decltype(v.size()), decltype(minmax_vec.size())> i = 0u; i < size; ++i) {
293  if (unlikely(safe_cast<int_type>(v[i]) < -minmax_vec[i] || safe_cast<int_type>(v[i]) > minmax_vec[i])) {
294  piranha_throw(std::invalid_argument, "a component of the vector to be encoded is out of bounds");
295  }
296  }
297  piranha_assert(minmax_vec[0u] > 0);
298  int_type retval = static_cast<int_type>(safe_cast<int_type>(v[0u]) + minmax_vec[0u]),
299  cur_c = static_cast<int_type>(2 * minmax_vec[0u] + 1);
300  piranha_assert(retval >= 0);
301  for (decltype(v.size()) i = 1u; i < size; ++i) {
302  retval = static_cast<int_type>(retval + ((safe_cast<int_type>(v[i]) + minmax_vec[i]) * cur_c));
303  piranha_assert(minmax_vec[i] > 0);
304  cur_c = static_cast<int_type>(cur_c * (2 * minmax_vec[i] + 1));
305  }
306  return static_cast<int_type>(retval + std::get<1u>(limit));
307  }
309 
329  template <typename Vector>
330  static void decode(Vector &retval, const int_type &n)
331  {
332  typedef typename Vector::value_type v_type;
333  const auto m = retval.size();
334  if (unlikely(m >= m_limits.size())) {
335  piranha_throw(std::invalid_argument, "size of vector to be decoded is too large");
336  }
337  if (unlikely(!m)) {
338  if (unlikely(n != 0)) {
339  piranha_throw(std::invalid_argument, "a vector of size 0 must always be encoded as 0");
340  }
341  return;
342  }
343  // Cache values.
344  const auto &limit = m_limits[m];
345  const auto &minmax_vec = std::get<0u>(limit);
346  const auto hmin = std::get<1u>(limit), hmax = std::get<2u>(limit);
347  if (unlikely(n < hmin || n > hmax)) {
348  piranha_throw(std::invalid_argument, "the integer to be decoded is out of bounds");
349  }
350  // NOTE: the static_cast here is useful when working with int_type == char. In that case,
351  // the binary operation on the RHS produces an int (due to integer promotion rules), which gets
352  // assigned back to char causing the compiler to complain about potentially lossy conversion.
353  const int_type code = static_cast<int_type>(n - hmin);
354  piranha_assert(code >= 0);
355  piranha_assert(minmax_vec[0u] > 0);
356  int_type mod_arg = static_cast<int_type>(2 * minmax_vec[0u] + 1);
357  // Do the first value manually.
358  retval[0u] = safe_cast<v_type>((code % mod_arg) - minmax_vec[0u]);
359  for (min_int<typename Vector::size_type, decltype(minmax_vec.size())> i = 1u; i < m; ++i) {
360  piranha_assert(minmax_vec[i] > 0);
361  retval[i] = safe_cast<v_type>((code % (mod_arg * (2 * minmax_vec[i] + 1))) / mod_arg - minmax_vec[i]);
362  mod_arg = static_cast<int_type>(mod_arg * (2 * minmax_vec[i] + 1));
363  }
364  }
365 };
366 
367 // Static initialization.
368 template <typename SignedInteger>
369 const typename kronecker_array<SignedInteger>::limits_type kronecker_array<SignedInteger>::m_limits
370  = kronecker_array<SignedInteger>::determine_limits();
371 }
372 
373 #endif
static const limits_type & get_limits()
Get the limits of the Kronecker codification.
Multiprecision integer class.
Definition: mp++.hpp:869
mp_integer< 1 > integer
Alias for piranha::mp_integer with 1 limb of static storage.
Definition: mp_integer.hpp:63
Exceptions.
std::size_t size_type
Size type.
typename detail::min_int_impl< T, Args... >::type min_int
Detect narrowest integer type.
#define piranha_throw(exception_type,...)
Exception-throwing macro.
Definition: exceptions.hpp:118
static int_type encode(const Vector &v)
Encode vector.
static void decode(Vector &retval, const int_type &n)
Decode into vector.
Root piranha namespace.
Definition: array_key.hpp:52
Type traits.
SignedInteger int_type
Signed integer type used for encoding.
safe_cast_type< To, From > safe_cast(const From &x)
Safe cast.
Definition: safe_cast.hpp:219