piranha  0.10
polynomial.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_POLYNOMIAL_HPP
30 #define PIRANHA_POLYNOMIAL_HPP
31 
32 #include <algorithm>
33 #include <boost/numeric/conversion/cast.hpp>
34 #include <cmath> // For std::ceil.
35 #include <cstddef>
36 #include <functional>
37 #include <initializer_list>
38 #include <iterator>
39 #include <limits>
40 #include <map>
41 #include <mutex>
42 #include <numeric>
43 #include <stdexcept>
44 #include <string>
45 #include <tuple>
46 #include <type_traits>
47 #include <utility>
48 #include <vector>
49 
50 #include <piranha/base_series_multiplier.hpp>
51 #include <piranha/config.hpp>
52 #include <piranha/debug_access.hpp>
53 #include <piranha/detail/atomic_flag_array.hpp>
54 #include <piranha/detail/cf_mult_impl.hpp>
55 #include <piranha/detail/divisor_series_fwd.hpp>
56 #include <piranha/detail/parallel_vector_transform.hpp>
57 #include <piranha/detail/poisson_series_fwd.hpp>
58 #include <piranha/detail/polynomial_fwd.hpp>
59 #include <piranha/detail/safe_integral_adder.hpp>
60 #include <piranha/detail/sfinae_types.hpp>
61 #include <piranha/exceptions.hpp>
62 #include <piranha/forwarding.hpp>
63 #include <piranha/ipow_substitutable_series.hpp>
64 #include <piranha/is_cf.hpp>
65 #include <piranha/key_is_multipliable.hpp>
66 #include <piranha/kronecker_array.hpp>
67 #include <piranha/kronecker_monomial.hpp>
68 #include <piranha/math.hpp>
69 #include <piranha/monomial.hpp>
70 #include <piranha/mp_integer.hpp>
71 #include <piranha/pow.hpp>
72 #include <piranha/power_series.hpp>
73 #include <piranha/safe_cast.hpp>
74 #include <piranha/series.hpp>
75 #include <piranha/series_multiplier.hpp>
76 #include <piranha/settings.hpp>
77 #include <piranha/substitutable_series.hpp>
78 #include <piranha/symbol_utils.hpp>
79 #include <piranha/t_substitutable_series.hpp>
80 #include <piranha/thread_pool.hpp>
81 #include <piranha/trigonometric_series.hpp>
82 #include <piranha/tuning.hpp>
83 #include <piranha/type_traits.hpp>
84 
85 namespace piranha
86 {
87 
88 namespace detail
89 {
90 
91 // Tag for polynomial instances.
92 struct polynomial_tag {
93 };
94 
95 // Type trait to check the key type in polynomial.
96 template <typename T>
97 struct is_polynomial_key {
98  static const bool value = false;
99 };
100 
101 template <typename T>
102 struct is_polynomial_key<kronecker_monomial<T>> {
103  static const bool value = true;
104 };
105 
106 template <typename T, typename U>
107 struct is_polynomial_key<monomial<T, U>> {
108  static const bool value = true;
109 };
110 
111 // Implementation detail to check if the monomial key supports the is_linear() method.
112 template <typename Key>
113 struct key_has_is_linear {
114  template <typename U>
115  using is_linear_t = decltype(std::declval<const U &>().is_linear(std::declval<const symbol_fset &>()));
116  static const bool value = std::is_same<detected_t<is_linear_t, Key>, std::pair<bool, symbol_idx>>::value;
117 };
118 }
119 
121 
148 template <typename Cf, typename Key>
150  : public power_series<
151  trigonometric_series<ipow_substitutable_series<
152  substitutable_series<t_substitutable_series<series<Cf, Key, polynomial<Cf, Key>>, polynomial<Cf, Key>>,
153  polynomial<Cf, Key>>,
154  polynomial<Cf, Key>>>,
155  polynomial<Cf, Key>>,
156  detail::polynomial_tag
157 {
158  // Check the key.
159  PIRANHA_TT_CHECK(detail::is_polynomial_key, Key);
160  // Make friend with debug class.
161  template <typename>
162  friend class debug_access;
163  // Make friend with all poly types.
164  template <typename, typename>
165  friend class polynomial;
166  // Make friend with Poisson series.
167  template <typename>
168  friend class poisson_series;
169  // Make friend with divisor series.
170  template <typename, typename>
171  friend class divisor_series;
172  // The base class.
173  using base = power_series<
179  // String constructor.
180  template <typename Str>
181  void construct_from_string(Str &&str)
182  {
183  using term_type = typename base::term_type;
184  // Insert the symbol.
185  this->m_symbol_set.emplace_hint(this->m_symbol_set.end(), std::forward<Str>(str));
186  // Construct and insert the term.
187  this->insert(term_type(Cf(1), typename term_type::key_type{1}));
188  }
189  template <typename T = Key,
190  enable_if_t<conjunction<detail::key_has_is_linear<T>, has_safe_cast<integer, Cf>>::value, int> = 0>
191  std::map<std::string, integer> integral_combination() const
192  {
193  std::map<std::string, integer> retval;
194  for (auto it = this->m_container.begin(); it != this->m_container.end(); ++it) {
195  const auto p = it->m_key.is_linear(this->m_symbol_set);
196  if (unlikely(!p.first)) {
197  piranha_throw(std::invalid_argument, "polynomial is not an integral linear combination");
198  }
199  retval[*(this->m_symbol_set.nth(p.second))] = safe_cast<integer>(it->m_cf);
200  }
201  return retval;
202  }
203  template <
204  typename T = Key,
205  enable_if_t<disjunction<negation<detail::key_has_is_linear<T>>, negation<has_safe_cast<integer, Cf>>>::value,
206  int> = 0>
207  std::map<std::string, integer> integral_combination() const
208  {
209  piranha_throw(std::invalid_argument,
210  "the polynomial type does not support the extraction of a linear combination");
211  }
212  // Integration utils.
213  // Empty for SFINAE.
214  template <typename T, typename = void>
215  struct integrate_type_ {
216  };
217  // The type resulting from the integration of the key of series T.
218  template <typename T>
219  using key_integrate_type
220  = decltype(std::declval<const typename T::term_type::key_type &>()
221  .integrate(std::declval<const std::string &>(), std::declval<const symbol_fset &>())
222  .first);
223  // Basic integration requirements for series T, to be satisfied both when the coefficient is integrable
224  // and when it is not. ResT is the type of the result of the integration.
225  template <typename T, typename ResT>
226  using basic_integrate_requirements = typename std::enable_if<
227  // Coefficient differentiable, and can call is_zero on the result.
229  std::declval<const std::string &>()))>::value
230  &&
231  // The key is integrable.
232  detail::true_tt<key_integrate_type<T>>::value &&
233  // The result needs to be addable in-place.
235  // It also needs to be ctible from zero.
236  std::is_constructible<ResT, int>::value>::type;
237  // Non-integrable coefficient.
238  template <typename T>
239  using nic_res_type = decltype((std::declval<const T &>() * std::declval<const typename T::term_type::cf_type &>())
240  / std::declval<const key_integrate_type<T> &>());
241  template <typename T>
242  struct integrate_type_<
243  T, typename std::enable_if<!is_integrable<typename T::term_type::cf_type>::value
244  && detail::true_tt<basic_integrate_requirements<T, nic_res_type<T>>>::value>::type> {
245  using type = nic_res_type<T>;
246  };
247  // Integrable coefficient.
248  // The type resulting from the differentiation of the key of series T.
249  template <typename T>
250  using key_partial_type
251  = decltype(std::declval<const typename T::term_type::key_type &>()
252  .partial(std::declval<const symbol_idx &>(), std::declval<const symbol_fset &>())
253  .first);
254  // Type resulting from the integration of the coefficient.
255  template <typename T>
256  using i_cf_type = decltype(
257  math::integrate(std::declval<const typename T::term_type::cf_type &>(), std::declval<const std::string &>()));
258  // Type above, multiplied by the type coming out of the derivative of the key.
259  template <typename T>
260  using i_cf_type_p = decltype(std::declval<const i_cf_type<T> &>() * std::declval<const key_partial_type<T> &>());
261  // Final series type.
262  template <typename T>
263  using ic_res_type = decltype(std::declval<const i_cf_type_p<T> &>() * std::declval<const T &>());
264  template <typename T>
265  struct integrate_type_<
266  T, typename std::enable_if<
267  is_integrable<typename T::term_type::cf_type>::value
268  && detail::true_tt<basic_integrate_requirements<T, ic_res_type<T>>>::value &&
269  // We need to be able to add the non-integrable type.
270  is_addable_in_place<ic_res_type<T>, nic_res_type<T>>::value &&
271  // We need to be able to compute the partial degree and cast it to integer.
272  has_safe_cast<integer,
273  decltype(std::declval<const typename T::term_type::key_type &>().degree(
274  std::declval<const symbol_idx_fset &>(), std::declval<const symbol_fset &>()))>::value
275  &&
276  // This is required in the initialisation of the return value.
277  std::is_constructible<i_cf_type_p<T>, i_cf_type<T>>::value &&
278  // We need to be able to assign the integrated coefficient times key partial.
279  std::is_assignable<i_cf_type_p<T> &, i_cf_type_p<T>>::value &&
280  // Needs math::negate().
281  has_negate<i_cf_type_p<T>>::value>::type> {
282  using type = ic_res_type<T>;
283  };
284  // Final typedef.
285  template <typename T>
286  using integrate_type = typename std::enable_if<is_returnable<typename integrate_type_<T>::type>::value,
287  typename integrate_type_<T>::type>::type;
288  // Integration with integrable coefficient.
289  template <typename T = polynomial>
290  integrate_type<T> integrate_impl(const std::string &s, const typename base::term_type &term,
291  const std::true_type &) const
292  {
293  typedef typename base::term_type term_type;
294  typedef typename term_type::cf_type cf_type;
295  typedef typename term_type::key_type key_type;
296  // Get the partial degree of the monomial in integral form.
297  integer degree;
298  const auto idx = symbol_idx_fset{ss_index_of(this->m_symbol_set, s)};
299  try {
300  // Check if s is actually in the symbol set or not.
301  if (*idx.begin() < this->m_symbol_set.size()) {
302  degree = safe_cast<integer>(term.m_key.degree(idx, this->m_symbol_set));
303  } else {
304  degree = 0;
305  }
306  } catch (const safe_cast_failure &) {
307  piranha_throw(std::invalid_argument,
308  "unable to perform polynomial integration: cannot extract the integral form of an exponent");
309  }
310  // If the degree is negative, integration by parts won't terminate.
311  if (degree.sgn() < 0) {
312  piranha_throw(std::invalid_argument,
313  "unable to perform polynomial integration: negative integral exponent");
314  }
315  polynomial tmp;
316  tmp.set_symbol_set(this->m_symbol_set);
317  key_type tmp_key = term.m_key;
318  tmp.insert(term_type(cf_type(1), tmp_key));
319  i_cf_type_p<T> i_cf(math::integrate(term.m_cf, s));
320  integrate_type<T> retval(i_cf * tmp);
321  for (integer i(1); i <= degree; ++i) {
322  // Update coefficient and key. These variables are persistent across loop iterations.
323  auto partial_key = tmp_key.partial(*idx.begin(), this->m_symbol_set);
324  i_cf = math::integrate(i_cf, s) * std::move(partial_key.first);
325  // Account for (-1)**i.
326  math::negate(i_cf);
327  // Build the other factor from the derivative of the monomial.
328  tmp = polynomial{};
329  tmp.set_symbol_set(this->m_symbol_set);
330  tmp_key = std::move(partial_key.second);
331  // NOTE: don't move tmp_key, as it needs to hold a valid value
332  // for the next loop iteration.
333  tmp.insert(term_type(cf_type(1), tmp_key));
334  retval += i_cf * tmp;
335  }
336  return retval;
337  }
338  // Integration with non-integrable coefficient.
339  template <typename T = polynomial>
340  integrate_type<T> integrate_impl(const std::string &, const typename base::term_type &,
341  const std::false_type &) const
342  {
343  piranha_throw(std::invalid_argument,
344  "unable to perform polynomial integration: coefficient type is not integrable");
345  }
346  // Template alias for use in pow() overload. Will check via SFINAE that the base pow() method can be called with
347  // argument T and that exponentiation of key type is legal.
348  template <typename T>
349  using key_pow_t
350  = decltype(std::declval<Key const &>().pow(std::declval<const T &>(), std::declval<const symbol_fset &>()));
351  template <typename T>
352  using pow_ret_type = enable_if_t<is_detected<key_pow_t, T>::value,
353  decltype(std::declval<series<Cf, Key, polynomial<Cf, Key>> const &>().pow(
354  std::declval<const T &>()))>;
355  // Invert utils.
356  template <typename Series>
357  using inverse_type = decltype(std::declval<const Series &>().pow(-1));
358  // Auto-truncation machinery.
359  // The degree and partial degree types, detected via math::degree().
360  template <typename T>
361  using degree_type = decltype(math::degree(std::declval<const T &>()));
362  template <typename T>
363  using pdegree_type = decltype(math::degree(std::declval<const T &>(), std::declval<const symbol_fset &>()));
364  // Enablers for auto-truncation: degree and partial degree must be the same, series must support
365  // math::truncate_degree(), degree type must be subtractable and yield the same type.
366  template <typename T>
367  using at_degree_enabler = typename std::enable_if<
368  std::is_same<degree_type<T>, pdegree_type<T>>::value && has_truncate_degree<T, degree_type<T>>::value
369  && std::is_same<decltype(std::declval<const degree_type<T> &>() - std::declval<const degree_type<T> &>()),
370  degree_type<T>>::value
371  && is_equality_comparable<degree_type<T>>::value,
372  int>::type;
373  // For the setter, we need the above plus we need to be able to convert safely U to the degree type.
374  template <typename T, typename U>
375  using at_degree_set_enabler =
376  typename std::enable_if<detail::true_tt<at_degree_enabler<T>>::value && has_safe_cast<degree_type<T>, U>::value,
377  int>::type;
378  // This needs to be separate from the other static inits because we don't have anything to init
379  // if the series does not support degree computation.
380  // NOTE: here the important thing is that this method does not return the same object for different series types,
381  // as the intent of the truncation mechanism is that each polynomial type has its own settings.
382  // We need to keep this in mind if we need static resources that must be unique for the series type, sometimes
383  // adding the Derived series as template argument in a toolbox might actually be necessary because of this. Note
384  // that, contrary to the, e.g., custom derivatives map in series.hpp here we don't care about the type of T - we
385  // just need to be able to extract the term type from it.
386  template <typename T = polynomial>
387  static degree_type<T> &get_at_degree_max()
388  {
389  // Init to zero for peace of mind - though this should never be accessed
390  // if the auto-truncation is not used.
391  static degree_type<T> at_degree_max(0);
392  return at_degree_max;
393  }
394  // Enabler for string construction.
395  template <typename Str>
396  using str_enabler =
397  typename std::enable_if<std::is_same<typename std::decay<Str>::type, std::string>::value
398  || std::is_same<typename std::decay<Str>::type, char *>::value
399  || std::is_same<typename std::decay<Str>::type, const char *>::value,
400  int>::type;
401  // Implementation of find_cf().
402  template <typename T>
403  using find_cf_enabler = typename std::enable_if<
404  std::is_constructible<typename base::term_type::key_type, decltype(std::begin(std::declval<const T &>())),
405  decltype(std::end(std::declval<const T &>())), const symbol_fset &>::value
407  int>::type;
408  template <typename T>
409  using find_cf_init_list_enabler = find_cf_enabler<std::initializer_list<T>>;
410  template <typename Iterator>
411  Cf find_cf_impl(Iterator begin, Iterator end) const
412  {
413  typename base::term_type tmp_term{Cf(0), Key(begin, end, this->m_symbol_set)};
414  auto it = this->m_container.find(tmp_term);
415  if (it == this->m_container.end()) {
416  return Cf(0);
417  }
418  return it->m_cf;
419  }
420  // Enabler for untruncated multiplication.
421  template <typename T>
422  using um_enabler =
423  typename std::enable_if<std::is_same<T, decltype(std::declval<const T &>() * std::declval<const T &>())>::value,
424  int>::type;
425  // Enabler for truncated multiplication.
426  template <typename T, typename U>
427  using tm_enabler =
428  typename std::enable_if<std::is_same<T, decltype(std::declval<const T &>() * std::declval<const T &>())>::value
429  && has_safe_cast<degree_type<T>, U>::value
430  && detail::true_tt<at_degree_enabler<T>>::value,
431  int>::type;
432  // Common bits for truncated/untruncated multiplication. Will do the usual merging of the symbol sets
433  // before calling the runner functor, which performs the actual multiplication.
434  template <typename Functor>
435  static polynomial um_tm_implementation(const polynomial &p1, const polynomial &p2, const Functor &runner)
436  {
437  return series_merge_f(p1, p2, runner);
438  }
439  // Helper function to clear the pow cache when a new auto truncation limit is set.
440  template <typename T>
441  static void truncation_clear_pow_cache(int mode, const T &max_degree, const symbol_fset &names)
442  {
443  // The pow cache is cleared only if we are actually changing the truncation settings.
444  if (s_at_degree_mode != mode || get_at_degree_max() != max_degree || names != s_at_degree_names) {
446  }
447  }
448 
449 public:
451  template <typename Cf2>
454 
457  polynomial() = default;
459  polynomial(const polynomial &) = default;
461  polynomial(polynomial &&) = default;
463 
479  template <typename Str, str_enabler<Str> = 0>
480  explicit polynomial(Str &&name) : base()
481  {
482  construct_from_string(std::forward<Str>(name));
483  }
487  {
488  PIRANHA_TT_CHECK(is_cf, polynomial);
489  PIRANHA_TT_CHECK(is_series, polynomial);
490  }
492 
499  polynomial &operator=(const polynomial &other) = default;
501 
506  polynomial &operator=(polynomial &&other) = default;
509 
529  template <typename T>
530  pow_ret_type<T> pow(const T &x) const
531  {
532  using ret_type = pow_ret_type<T>;
533  typedef typename ret_type::term_type term_type;
534  typedef typename term_type::cf_type cf_type;
535  typedef typename term_type::key_type key_type;
536  if (this->size() == 1u && !this->m_container.begin()->m_key.is_unitary(this->m_symbol_set)) {
537  cf_type cf(math::pow(this->m_container.begin()->m_cf, x));
538  key_type key(this->m_container.begin()->m_key.pow(x, this->m_symbol_set));
539  ret_type retval;
540  retval.set_symbol_set(this->m_symbol_set);
541  retval.insert(term_type(std::move(cf), std::move(key)));
542  return retval;
543  }
544  return static_cast<series<Cf, Key, polynomial<Cf, Key>> const *>(this)->pow(x);
545  }
547 
556  template <typename Series = polynomial>
557  inverse_type<Series> invert() const
558  {
559  return this->pow(-1);
560  }
562 
590  template <typename T = polynomial>
591  integrate_type<T> integrate(const std::string &name) const
592  {
593  typedef typename base::term_type term_type;
594  typedef typename term_type::cf_type cf_type;
595  integrate_type<T> retval(0);
596  // A copy of the current symbol set plus name. If name is
597  // in the set already, it will be just a copy.
598  const auto aug_ss = [this, &name]() -> symbol_fset {
599  symbol_fset tmp_ss(this->m_symbol_set);
600  tmp_ss.insert(name);
601  return tmp_ss;
602  }();
603  const auto it_f = this->m_container.end();
604  for (auto it = this->m_container.begin(); it != it_f; ++it) {
605  // If the derivative of the coefficient is null, we just need to deal with
606  // the integration of the key.
607  if (math::is_zero(math::partial(it->m_cf, name))) {
608  polynomial tmp;
609  tmp.set_symbol_set(aug_ss);
610  auto key_int = it->m_key.integrate(name, this->m_symbol_set);
611  tmp.insert(term_type(cf_type(1), std::move(key_int.second)));
612  retval += (tmp * it->m_cf) / key_int.first;
613  } else {
614  retval += integrate_impl(name, *it, std::integral_constant<bool, is_integrable<cf_type>::value>{});
615  }
616  }
617  return retval;
618  }
620 
636  template <typename U, typename T = polynomial, at_degree_set_enabler<T, U> = 0>
637  static void set_auto_truncate_degree(const U &max_degree)
638  {
639  // Init out for exception safety.
640  auto new_degree = safe_cast<degree_type<T>>(max_degree);
641  // Initialisation of function-level statics is thread-safe, no need to lock. We get
642  // a ref before the lock because the initialisation of the static could throw in principle,
643  // and we want the section after the lock to be exception-free.
644  auto &at_dm = get_at_degree_max();
645  std::lock_guard<std::mutex> lock(s_at_degree_mutex);
646  // NOTE: here in principle there could be an exception thrown as a consequence of the degree comparison.
647  // This is not a problem as at this stage no setting has been modified.
648  truncation_clear_pow_cache(1, new_degree, symbol_fset{});
649  s_at_degree_mode = 1;
650  // NOTE: the degree type of polys satisfies is_container_element, so move assignment is noexcept.
651  at_dm = std::move(new_degree);
652  // This should not throw (a vector of strings, destructors and deallocation should be noexcept).
653  s_at_degree_names.clear();
654  }
656 
675  template <typename U, typename T = polynomial, at_degree_set_enabler<T, U> = 0>
676  static void set_auto_truncate_degree(const U &max_degree, const symbol_fset &names)
677  {
678  // Copy+move for exception safety.
679  auto new_degree = safe_cast<degree_type<T>>(max_degree);
680  auto new_names = names;
681  auto &at_dm = get_at_degree_max();
682  std::lock_guard<std::mutex> lock(s_at_degree_mutex);
683  truncation_clear_pow_cache(2, new_degree, new_names);
684  s_at_degree_mode = 2;
685  at_dm = std::move(new_degree);
686  s_at_degree_names = std::move(new_names);
687  }
689 
700  template <typename T = polynomial, at_degree_enabler<T> = 0>
702  {
703  degree_type<T> new_degree(0);
704  auto &at_dm = get_at_degree_max();
705  std::lock_guard<std::mutex> lock(s_at_degree_mutex);
706  s_at_degree_mode = 0;
707  at_dm = std::move(new_degree);
708  s_at_degree_names.clear();
709  }
711 
726  template <typename T = polynomial, at_degree_enabler<T> = 0>
727  static std::tuple<int, degree_type<T>, symbol_fset> get_auto_truncate_degree()
728  {
729  std::lock_guard<std::mutex> lock(s_at_degree_mutex);
730  return std::make_tuple(s_at_degree_mode, get_at_degree_max(), s_at_degree_names);
731  }
733 
753  template <typename T, find_cf_enabler<T> = 0>
754  Cf find_cf(const T &c) const
755  {
756  return find_cf_impl(std::begin(c), std::end(c));
757  }
759 
773  template <typename T, find_cf_init_list_enabler<T> = 0>
774  Cf find_cf(std::initializer_list<T> l) const
775  {
776  return find_cf_impl(std::begin(l), std::end(l));
777  }
779 
799  template <typename T = polynomial, um_enabler<T> = 0>
801  {
802  auto runner = [](const polynomial &a, const polynomial &b) {
803  return series_multiplier<polynomial>(a, b)._untruncated_multiplication();
804  };
805  return um_tm_implementation(p1, p2, runner);
806  }
808 
834  template <typename U, typename T = polynomial, tm_enabler<T, U> = 0>
835  static polynomial truncated_multiplication(const polynomial &p1, const polynomial &p2, const U &max_degree)
836  {
837  // NOTE: these 2 implementations may be rolled into one once we can safely capture variadic arguments
838  // in lambdas.
839  auto runner = [&max_degree](const polynomial &a, const polynomial &b) {
840  return series_multiplier<polynomial>(a, b)._truncated_multiplication(safe_cast<degree_type<T>>(max_degree));
841  };
842  return um_tm_implementation(p1, p2, runner);
843  }
845 
871  template <typename U, typename T = polynomial, tm_enabler<T, U> = 0>
872  static polynomial truncated_multiplication(const polynomial &p1, const polynomial &p2, const U &max_degree,
873  const symbol_fset &names)
874  {
875  // NOTE: total and partial degree must be the same.
876  auto runner = [&max_degree, &names](const polynomial &a, const polynomial &b) -> polynomial {
877  const auto idx = ss_intersect_idx(a.get_symbol_set(), names);
878  return series_multiplier<polynomial>(a, b)._truncated_multiplication(safe_cast<degree_type<T>>(max_degree),
879  names, idx);
880  };
881  return um_tm_implementation(p1, p2, runner);
882  }
883 
884 private:
885  // Static data for auto_truncate_degree.
886  static std::mutex s_at_degree_mutex;
887  static int s_at_degree_mode;
888  static symbol_fset s_at_degree_names;
889 };
890 
891 // Static inits.
892 template <typename Cf, typename Key>
893 std::mutex polynomial<Cf, Key>::s_at_degree_mutex;
894 
895 template <typename Cf, typename Key>
896 int polynomial<Cf, Key>::s_at_degree_mode = 0;
897 
898 template <typename Cf, typename Key>
899 symbol_fset polynomial<Cf, Key>::s_at_degree_names;
900 
901 namespace detail
902 {
903 
904 // Identification of key types for dispatching in the multiplier.
905 template <typename T>
906 struct is_kronecker_monomial {
907  static const bool value = false;
908 };
909 
910 template <typename T>
911 struct is_kronecker_monomial<kronecker_monomial<T>> {
912  static const bool value = true;
913 };
914 
915 template <typename T>
916 struct is_monomial {
917  static const bool value = false;
918 };
919 
920 template <typename T, typename S>
921 struct is_monomial<monomial<T, S>> {
922  static const bool value = true;
923 };
924 
925 // Identify the presence of auto-truncation methods in the poly multiplier.
926 template <typename S, typename T>
927 class has_set_auto_truncate_degree : sfinae_types
928 {
929  // NOTE: if we have total degree auto truncation, we also have partial degree truncation.
930  template <typename S1, typename T1>
931  static auto test(const S1 &, const T1 &t) -> decltype(S1::set_auto_truncate_degree(t), void(), yes());
932  static no test(...);
933 
934 public:
935  static const bool value = std::is_same<yes, decltype(test(std::declval<S>(), std::declval<T>()))>::value;
936 };
937 
938 template <typename S, typename T>
939 const bool has_set_auto_truncate_degree<S, T>::value;
940 
941 template <typename S>
942 class has_get_auto_truncate_degree : sfinae_types
943 {
944  template <typename S1>
945  static auto test(const S1 &) -> decltype(S1::get_auto_truncate_degree(), void(), yes());
946  static no test(...);
947 
948 public:
949  static const bool value = std::is_same<yes, decltype(test(std::declval<S>()))>::value;
950 };
951 
952 template <typename S>
953 const bool has_get_auto_truncate_degree<S>::value;
954 
955 // Global enabler for the polynomial multiplier.
956 template <typename Series>
957 using poly_multiplier_enabler = typename std::enable_if<std::is_base_of<detail::polynomial_tag, Series>::value>::type;
958 }
959 
961 
977 template <typename Series>
978 class series_multiplier<Series, detail::poly_multiplier_enabler<Series>> : public base_series_multiplier<Series>
979 {
980  // Base multiplier type.
982  // Cf type getter shortcut.
983  template <typename T>
984  using cf_t = typename T::term_type::cf_type;
985  // Key type getter shortcut.
986  template <typename T>
987  using key_t = typename T::term_type::key_type;
988  // Bounds checking.
989  // Functor to return un updated copy of p if v is less than p.first or greater than p.second.
990  struct update_minmax {
991  template <typename T>
992  std::pair<T, T> operator()(const std::pair<T, T> &p, const T &v) const
993  {
994  return std::make_pair(v < p.first ? v : p.first, v > p.second ? v : p.second);
995  }
996  };
997  // No bounds checking if key is a monomial with non-integral exponents.
998  template <
999  typename T = Series,
1000  typename std::enable_if<
1001  detail::is_monomial<key_t<T>>::value && !std::is_integral<typename key_t<T>::value_type>::value, int>::type
1002  = 0>
1003  void check_bounds() const
1004  {
1005  }
1006  // Monomial with integral exponents.
1007  template <
1008  typename T = Series,
1009  typename std::enable_if<
1010  detail::is_monomial<key_t<T>>::value && std::is_integral<typename key_t<T>::value_type>::value, int>::type
1011  = 0>
1012  void check_bounds() const
1013  {
1014  using expo_type = typename key_t<T>::value_type;
1015  using term_type = typename Series::term_type;
1016  using mm_vec = std::vector<std::pair<expo_type, expo_type>>;
1017  using v_ptr = typename base::v_ptr;
1018  // NOTE: we know that the input series are not null.
1019  piranha_assert(this->m_v1.size() != 0u && this->m_v2.size() != 0u);
1020  // Sync mutex, actually used only in mt mode.
1021  std::mutex mut;
1022  // Checker for monomial sizes in debug mode.
1023  auto monomial_checker = [this](const term_type &t) { return t.m_key.size() == this->m_ss.size(); };
1024  (void)monomial_checker;
1025  // The function used to determine minmaxs for the two series. This is used both in
1026  // single-thread and multi-thread mode.
1027  auto thread_func = [&mut, this, &monomial_checker](unsigned t_idx, const v_ptr *vp, mm_vec *mmv) {
1028  piranha_assert(t_idx < this->m_n_threads);
1029  // Establish the block size.
1030  const auto block_size = vp->size() / this->m_n_threads;
1031  auto start = vp->data() + t_idx * block_size;
1032  const auto end
1033  = vp->data() + ((t_idx == this->m_n_threads - 1u) ? vp->size() : ((t_idx + 1u) * block_size));
1034  // We need to make sure we have at least 1 element to process. This is guaranteed
1035  // in the single-threaded implementation but not in multithreading.
1036  if (start == end) {
1037  piranha_assert(this->m_n_threads > 1u);
1038  return;
1039  }
1040  piranha_assert(monomial_checker(**start));
1041  // Local vector that will hold the minmax values for this thread.
1042  mm_vec minmax_values;
1043  // Init the mimnax.
1044  // NOTE: we can use this as we are sure the series has at least one element (start != end).
1045  std::transform((*start)->m_key.begin(), (*start)->m_key.end(), std::back_inserter(minmax_values),
1046  [](const expo_type &v) { return std::make_pair(v, v); });
1047  // Move to the next element and go with the loop.
1048  ++start;
1049  for (; start != end; ++start) {
1050  piranha_assert(monomial_checker(**start));
1051  // NOTE: std::transform is allowed to do transformations in-place - i.e., here the output range is
1052  // the same as the first or second input range:
1053  // http://stackoverflow.com/questions/19200528/is-it-safe-for-the-input-iterator-and-output-iterator-in-stdtransform-to-be-fr
1054  // The important part is that the functor *itself* must not mutate the elements.
1055  std::transform(minmax_values.begin(), minmax_values.end(), (*start)->m_key.begin(),
1056  minmax_values.begin(), update_minmax{});
1057  }
1058  if (this->m_n_threads == 1u) {
1059  // In single thread the output mmv should be written only once, after being def-inited.
1060  piranha_assert(mmv->empty());
1061  // Just move in the local minmax values in single-threaded mode.
1062  *mmv = std::move(minmax_values);
1063  } else {
1064  std::lock_guard<std::mutex> lock(mut);
1065  if (mmv->empty()) {
1066  // If mmv has not been inited yet, just move in the current values.
1067  *mmv = std::move(minmax_values);
1068  } else {
1069  piranha_assert(minmax_values.size() == mmv->size());
1070  // Otherwise, update the minmaxs.
1071  auto updater
1072  = [](const std::pair<expo_type, expo_type> &p1, const std::pair<expo_type, expo_type> &p2) {
1073  return std::make_pair(p1.first < p2.first ? p1.first : p2.first,
1074  p1.second > p2.second ? p1.second : p2.second);
1075  };
1076  std::transform(minmax_values.begin(), minmax_values.end(), mmv->begin(), mmv->begin(), updater);
1077  }
1078  }
1079  };
1080  // minmax vectors for the two series.
1081  mm_vec minmax_values1, minmax_values2;
1082  check_bounds_impl(minmax_values1, minmax_values2, thread_func);
1083  // Compute the sum of the two minmaxs, using multiprecision to avoid overflow (this is a simple interval
1084  // addition).
1085  std::vector<std::pair<integer, integer>> minmax_values;
1086  std::transform(
1087  minmax_values1.begin(), minmax_values1.end(), minmax_values2.begin(), std::back_inserter(minmax_values),
1088  [](const std::pair<expo_type, expo_type> &p1, const std::pair<expo_type, expo_type> &p2) {
1089  return std::make_pair(integer(p1.first) + integer(p2.first), integer(p1.second) + integer(p2.second));
1090  });
1091  piranha_assert(minmax_values.size() == minmax_values1.size());
1092  piranha_assert(minmax_values.size() == minmax_values2.size());
1093  // Now do the checking.
1094  for (decltype(minmax_values.size()) i = 0u; i < minmax_values.size(); ++i) {
1095  try {
1096  (void)static_cast<expo_type>(minmax_values[i].first);
1097  (void)static_cast<expo_type>(minmax_values[i].second);
1098  } catch (...) {
1099  piranha_throw(std::overflow_error, "monomial components are out of bounds");
1100  }
1101  }
1102  }
1103  template <typename T = Series,
1104  typename std::enable_if<detail::is_kronecker_monomial<key_t<T>>::value, int>::type = 0>
1105  void check_bounds() const
1106  {
1107  using value_type = typename key_t<Series>::value_type;
1108  using ka = kronecker_array<value_type>;
1109  using v_ptr = typename base::v_ptr;
1110  using mm_vec = std::vector<std::pair<value_type, value_type>>;
1111  piranha_assert(this->m_v1.size() != 0u && this->m_v2.size() != 0u);
1112  // NOTE: here we are sure about this since the symbol set in a series should never
1113  // overflow the size of the limits, as the check for compatibility in Kronecker monomial
1114  // would kick in.
1115  piranha_assert(this->m_ss.size() < ka::get_limits().size());
1116  std::mutex mut;
1117  auto thread_func = [&mut, this](unsigned t_idx, const v_ptr *vp, mm_vec *mmv) {
1118  piranha_assert(t_idx < this->m_n_threads);
1119  const auto block_size = vp->size() / this->m_n_threads;
1120  auto start = vp->data() + t_idx * block_size;
1121  const auto end
1122  = vp->data() + ((t_idx == this->m_n_threads - 1u) ? vp->size() : ((t_idx + 1u) * block_size));
1123  if (start == end) {
1124  piranha_assert(this->m_n_threads > 1u);
1125  return;
1126  }
1127  mm_vec minmax_values;
1128  // Tmp vector for unpacking, inited with the first element in the range.
1129  // NOTE: we need to check that the exponents of the monomials in the result do not
1130  // go outside the bounds of the Kronecker codification. We need to unpack all monomials
1131  // in the operands and examine them, we cannot operate on the codes for this.
1132  auto tmp_vec = (*start)->m_key.unpack(this->m_ss);
1133  // Init the mimnax.
1134  std::transform(tmp_vec.begin(), tmp_vec.end(), std::back_inserter(minmax_values),
1135  [](const value_type &v) { return std::make_pair(v, v); });
1136  ++start;
1137  for (; start != end; ++start) {
1138  tmp_vec = (*start)->m_key.unpack(this->m_ss);
1139  std::transform(minmax_values.begin(), minmax_values.end(), tmp_vec.begin(), minmax_values.begin(),
1140  update_minmax{});
1141  }
1142  if (this->m_n_threads == 1u) {
1143  piranha_assert(mmv->empty());
1144  *mmv = std::move(minmax_values);
1145  } else {
1146  std::lock_guard<std::mutex> lock(mut);
1147  if (mmv->empty()) {
1148  *mmv = std::move(minmax_values);
1149  } else {
1150  piranha_assert(minmax_values.size() == mmv->size());
1151  auto updater
1152  = [](const std::pair<value_type, value_type> &p1, const std::pair<value_type, value_type> &p2) {
1153  return std::make_pair(p1.first < p2.first ? p1.first : p2.first,
1154  p1.second > p2.second ? p1.second : p2.second);
1155  };
1156  std::transform(minmax_values.begin(), minmax_values.end(), mmv->begin(), mmv->begin(), updater);
1157  }
1158  }
1159  };
1160  mm_vec minmax_values1, minmax_values2;
1161  check_bounds_impl(minmax_values1, minmax_values2, thread_func);
1162  std::vector<std::pair<integer, integer>> minmax_values;
1163  std::transform(
1164  minmax_values1.begin(), minmax_values1.end(), minmax_values2.begin(), std::back_inserter(minmax_values),
1165  [](const std::pair<value_type, value_type> &p1, const std::pair<value_type, value_type> &p2) {
1166  return std::make_pair(integer(p1.first) + integer(p2.first), integer(p1.second) + integer(p2.second));
1167  });
1168  // Bounds of the Kronecker representation for each component.
1169  const auto &minmax_vec
1170  = std::get<0u>(ka::get_limits()[static_cast<decltype(ka::get_limits().size())>(this->m_ss.size())]);
1171  piranha_assert(minmax_values.size() == minmax_vec.size());
1172  piranha_assert(minmax_values.size() == minmax_values1.size());
1173  piranha_assert(minmax_values.size() == minmax_values2.size());
1174  for (decltype(minmax_values.size()) i = 0u; i < minmax_values.size(); ++i) {
1175  if (unlikely(minmax_values[i].first < -minmax_vec[i] || minmax_values[i].second > minmax_vec[i])) {
1176  piranha_throw(std::overflow_error, "Kronecker monomial components are out of bounds");
1177  }
1178  }
1179  }
1180  // Implementation detail of the bound checking logic. This is common enough to be shared.
1181  template <typename MmVec, typename Func>
1182  void check_bounds_impl(MmVec &minmax_values1, MmVec &minmax_values2, Func &thread_func) const
1183  {
1184  if (this->m_n_threads == 1u) {
1185  thread_func(0u, &(this->m_v1), &minmax_values1);
1186  thread_func(0u, &(this->m_v2), &minmax_values2);
1187  } else {
1188  // Series 1.
1189  {
1190  future_list<void> ff_list;
1191  try {
1192  for (unsigned i = 0u; i < this->m_n_threads; ++i) {
1193  ff_list.push_back(thread_pool::enqueue(i, thread_func, i, &(this->m_v1), &minmax_values1));
1194  }
1195  // First let's wait for everything to finish.
1196  ff_list.wait_all();
1197  // Then, let's handle the exceptions.
1198  ff_list.get_all();
1199  } catch (...) {
1200  ff_list.wait_all();
1201  throw;
1202  }
1203  }
1204  // Series 2.
1205  {
1206  future_list<void> ff_list;
1207  try {
1208  for (unsigned i = 0u; i < this->m_n_threads; ++i) {
1209  ff_list.push_back(thread_pool::enqueue(i, thread_func, i, &(this->m_v2), &minmax_values2));
1210  }
1211  // First let's wait for everything to finish.
1212  ff_list.wait_all();
1213  // Then, let's handle the exceptions.
1214  ff_list.get_all();
1215  } catch (...) {
1216  ff_list.wait_all();
1217  throw;
1218  }
1219  }
1220  }
1221  }
1222  // Enabler for the call operator.
1223  template <typename T>
1224  using call_enabler = typename std::enable_if<
1225  key_is_multipliable<cf_t<T>, key_t<T>>::value && has_multiply_accumulate<cf_t<T>>::value, int>::type;
1226  // Utility helpers for the subtraction of degree types in the truncation routines. The specialisation
1227  // for integral types will check the operation for overflow.
1228  template <typename T, typename std::enable_if<!std::is_integral<T>::value, int>::type = 0>
1229  static T degree_sub(const T &a, const T &b)
1230  {
1231  return a - b;
1232  }
1233  template <typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
1234  static T degree_sub(const T &a, const T &b)
1235  {
1236  T retval(a);
1237  detail::safe_integral_subber(retval, b);
1238  return retval;
1239  }
1240  // Dispatch of untruncated multiplication.
1241  template <typename T = Series,
1242  typename std::enable_if<detail::is_kronecker_monomial<typename T::term_type::key_type>::value, int>::type
1243  = 0>
1244  Series um_impl() const
1245  {
1246  return untruncated_kronecker_mult();
1247  }
1248  template <typename T = Series,
1249  typename std::enable_if<!detail::is_kronecker_monomial<typename T::term_type::key_type>::value, int>::type
1250  = 0>
1251  Series um_impl() const
1252  {
1253  return this->plain_multiplication();
1254  }
1255 
1256 public:
1258 
1278  explicit series_multiplier(const Series &s1, const Series &s2) : base(s1, s2)
1279  {
1280  // Nothing to do if the series are null or the merged symbol set is empty.
1281  if (unlikely(this->m_v1.empty() || this->m_v2.empty() || this->m_ss.size() == 0u)) {
1282  return;
1283  }
1284  check_bounds();
1285  }
1287 
1319  template <typename T = Series, call_enabler<T> = 0>
1320  Series operator()() const
1321  {
1322  return execute();
1323  }
1328 
1354  {
1355  return um_impl();
1356  }
1358 
1389  template <typename T, typename... Args>
1390  Series _truncated_multiplication(const T &max_degree, const Args &... args) const
1391  {
1392  // NOTE: a possible optimisation here is the following: if the sum degrees of the arguments is less than
1393  // or equal to the max truncation degree, just do the normal multiplication - which can also then take
1394  // advantage of faster Kronecker multiplication, if the series are suitable.
1395  using term_type = typename Series::term_type;
1396  // NOTE: degree type is the same in total and partial.
1397  using degree_type = decltype(ps_get_degree(term_type{}, this->m_ss));
1398  using size_type = typename base::size_type;
1399  namespace sph = std::placeholders;
1400  static_assert(std::is_same<T, degree_type>::value, "Invalid degree type");
1401  static_assert(detail::has_get_auto_truncate_degree<Series>::value, "Invalid series type");
1402  // First let's create two vectors with the degrees of the terms in the two series.
1403  using d_size_type = typename std::vector<degree_type>::size_type;
1404  std::vector<degree_type> v_d1(safe_cast<d_size_type>(this->m_v1.size())),
1405  v_d2(safe_cast<d_size_type>(this->m_v2.size()));
1406  detail::parallel_vector_transform(
1407  this->m_n_threads, this->m_v1, v_d1,
1408  std::bind(term_degree_getter{}, sph::_1, std::cref(this->m_ss), std::cref(args)...));
1409  detail::parallel_vector_transform(
1410  this->m_n_threads, this->m_v2, v_d2,
1411  std::bind(term_degree_getter{}, sph::_1, std::cref(this->m_ss), std::cref(args)...));
1412  // Next we need to order the terms in the second series, and also the corresponding degree vector.
1413  // First we create a vector of indices and we fill it.
1414  std::vector<size_type> idx_vector(safe_cast<typename std::vector<size_type>::size_type>(this->m_v2.size()));
1415  std::iota(idx_vector.begin(), idx_vector.end(), size_type(0u));
1416  // Second, we sort the vector of indices according to the degrees in the second series.
1417  std::stable_sort(idx_vector.begin(), idx_vector.end(), [&v_d2](const size_type &i1, const size_type &i2) {
1418  return v_d2[static_cast<d_size_type>(i1)] < v_d2[static_cast<d_size_type>(i2)];
1419  });
1420  // Finally, we apply the permutation to v_d2 and m_v2.
1421  decltype(this->m_v2) v2_copy(this->m_v2.size());
1422  decltype(v_d2) v_d2_copy(v_d2.size());
1423  std::transform(idx_vector.begin(), idx_vector.end(), v2_copy.begin(),
1424  [this](const size_type &i) { return this->m_v2[i]; });
1425  std::transform(idx_vector.begin(), idx_vector.end(), v_d2_copy.begin(),
1426  [&v_d2](const size_type &i) { return v_d2[static_cast<d_size_type>(i)]; });
1427  this->m_v2 = std::move(v2_copy);
1428  v_d2 = std::move(v_d2_copy);
1429  // Now get the skip limits and we build the limits functor.
1430  const auto sl = _get_skip_limits(v_d1, v_d2, max_degree);
1431  auto lf = [&sl](const size_type &idx1) {
1432  return sl[static_cast<typename std::vector<size_type>::size_type>(idx1)];
1433  };
1434  return this->plain_multiplication(lf);
1435  }
1437 
1463  template <typename T>
1464  std::vector<typename base::size_type> _get_skip_limits(const std::vector<T> &v_d1, const std::vector<T> &v_d2,
1465  const T &max_degree) const
1466  {
1467  // NOTE: this can be parallelised, but we need to check the heuristic
1468  // for selecting the number of threads as it is pretty fast wrt the multiplication.
1469  // Check that we are allowed to call this method.
1470  PIRANHA_TT_CHECK(detail::has_get_auto_truncate_degree, Series);
1471  PIRANHA_TT_CHECK(std::is_same, T, decltype(math::degree(Series{})));
1472  using size_type = typename base::size_type;
1473  using d_size_type = typename std::vector<T>::size_type;
1474  piranha_assert(std::is_sorted(v_d2.begin(), v_d2.end()));
1475  // A vector of indices into the second series.
1476  std::vector<size_type> idx_vector(safe_cast<typename std::vector<size_type>::size_type>(this->m_v2.size()));
1477  std::iota(idx_vector.begin(), idx_vector.end(), size_type(0u));
1478  // The return value.
1479  std::vector<size_type> retval;
1480  for (const auto &d1 : v_d1) {
1481  // Here we will find the index of the first term t2 in the second series such that
1482  // the degree d2 of t2 is > max_degree - d1, that is, d1 + d2 > max_degree.
1483  // NOTE: we need to use upper_bound, instead of lower_bound, because we need to find the first
1484  // element which is *strictly* greater than the max degree, as upper bound of a half closed
1485  // interval.
1486  // NOTE: the functor of lower_bound works inversely wrt upper_bound. See the notes on the type
1487  // requirements for the functor here:
1488  // http://en.cppreference.com/w/cpp/algorithm/upper_bound
1489  const T comp = degree_sub(max_degree, d1);
1490  const auto it = std::upper_bound(
1491  idx_vector.begin(), idx_vector.end(), comp,
1492  [&v_d2](const T &c, const size_type &idx) { return c < v_d2[static_cast<d_size_type>(idx)]; });
1493  retval.push_back(it == idx_vector.end() ? static_cast<size_type>(idx_vector.size()) : *it);
1494  }
1495  // Check the consistency of the result in debug mode.
1496  auto retval_checker = [&retval, &v_d1, &v_d2, &max_degree, this]() -> bool {
1497  for (decltype(retval.size()) i = 0u; i < retval.size(); ++i) {
1498  // NOTE: this just means that all terms in s2 are within the limit.
1499  if (retval[i] == v_d2.size()) {
1500  continue;
1501  }
1502  if (retval[i] > v_d2.size()) {
1503  return false;
1504  }
1505  if (!(v_d2[static_cast<d_size_type>(retval[i])]
1506  > this->degree_sub(max_degree, v_d1[static_cast<d_size_type>(i)]))) {
1507  return false;
1508  }
1509  }
1510  return true;
1511  };
1512  (void)retval_checker;
1513  piranha_assert(retval.size() == this->m_v1.size());
1514  piranha_assert(retval_checker());
1515  return retval;
1516  }
1518 private:
1519  // NOTE: wrapper to multadd that treats specially rational coefficients. We need to decide in the future
1520  // if this stays here or if it is better to generalise it.
1521  template <typename T, typename std::enable_if<!is_mp_rational<T>::value, int>::type = 0>
1522  static void fma_wrap(T &a, const T &b, const T &c)
1523  {
1524  math::multiply_accumulate(a, b, c);
1525  }
1526  template <typename T, typename std::enable_if<is_mp_rational<T>::value, int>::type = 0>
1527  static void fma_wrap(T &a, const T &b, const T &c)
1528  {
1529  math::multiply_accumulate(a._num(), b.num(), c.num());
1530  }
1531  // Wrapper for the plain multiplication routine.
1532  // Case 1: no auto truncation available, just run the plain multiplication.
1533  template <typename T = Series,
1534  typename std::enable_if<!detail::has_get_auto_truncate_degree<T>::value, int>::type = 0>
1535  Series plain_multiplication_wrapper() const
1536  {
1537  return this->plain_multiplication();
1538  }
1539  // Case 2: auto-truncation available. Check if auto truncation is active.
1540  template <typename T = Series,
1541  typename std::enable_if<detail::has_get_auto_truncate_degree<T>::value, int>::type = 0>
1542  Series plain_multiplication_wrapper() const
1543  {
1544  const auto t = T::get_auto_truncate_degree();
1545  if (std::get<0u>(t) == 0) {
1546  // No truncation active.
1547  return this->plain_multiplication();
1548  }
1549  // Truncation is active.
1550  if (std::get<0u>(t) == 1) {
1551  // Total degree truncation.
1552  return _truncated_multiplication(std::get<1u>(t));
1553  }
1554  piranha_assert(std::get<0u>(t) == 2);
1555  // Partial degree truncation.
1556  const auto idx = ss_intersect_idx(this->m_ss, std::get<2u>(t));
1557  return _truncated_multiplication(std::get<1u>(t), std::get<2u>(t), idx);
1558  }
1559  // NOTE: the existence of these functors is because GCC 4.8 has troubles capturing variadic arguments in lambdas
1560  // in _truncated_multiplication, and we need to use std::bind instead. Once we switch to 4.9, we can revert
1561  // to lambdas and drop the <functional> header.
1562  struct term_degree_sorter {
1563  using term_type = typename Series::term_type;
1564  template <typename... Args>
1565  bool operator()(term_type const *p1, term_type const *p2, const symbol_fset &ss, const Args &... args) const
1566  {
1567  return ps_get_degree(*p1, args..., ss) < ps_get_degree(*p2, args..., ss);
1568  }
1569  };
1570  struct term_degree_getter {
1571  using term_type = typename Series::term_type;
1572  template <typename... Args>
1573  auto operator()(term_type const *p, const symbol_fset &ss, const Args &... args) const
1574  -> decltype(ps_get_degree(*p, args..., ss))
1575  {
1576  return ps_get_degree(*p, args..., ss);
1577  }
1578  };
1579  // execute() is the top level dispatch for the actual multiplication.
1580  // Case 1: not a Kronecker monomial, do the plain mult.
1581  template <typename T = Series,
1582  typename std::enable_if<!detail::is_kronecker_monomial<typename T::term_type::key_type>::value, int>::type
1583  = 0>
1584  Series execute() const
1585  {
1586  return plain_multiplication_wrapper();
1587  }
1588  // Checking for active truncation.
1589  template <typename T = Series,
1590  typename std::enable_if<detail::has_get_auto_truncate_degree<T>::value, int>::type = 0>
1591  bool check_truncation() const
1592  {
1593  const auto t = T::get_auto_truncate_degree();
1594  return std::get<0u>(t) != 0;
1595  }
1596  template <typename T = Series,
1597  typename std::enable_if<!detail::has_get_auto_truncate_degree<T>::value, int>::type = 0>
1598  bool check_truncation() const
1599  {
1600  return false;
1601  }
1602  // Case 2: Kronecker mult, do the special multiplication unless a truncation is active. In that case, run the
1603  // plain mult.
1604  template <typename T = Series,
1605  typename std::enable_if<detail::is_kronecker_monomial<typename T::term_type::key_type>::value, int>::type
1606  = 0>
1607  Series execute() const
1608  {
1609  if (check_truncation()) {
1610  return plain_multiplication_wrapper();
1611  }
1612  return untruncated_kronecker_mult();
1613  }
1614  template <typename T = Series,
1615  typename std::enable_if<detail::is_kronecker_monomial<typename T::term_type::key_type>::value, int>::type
1616  = 0>
1617  Series untruncated_kronecker_mult() const
1618  {
1619  // Cache the sizes.
1620  const auto size1 = this->m_v1.size(), size2 = this->m_v2.size();
1621  // Determine whether we want to estimate or not. We check the threshold, and
1622  // we force the estimation in multithreaded mode.
1623  bool estimate = true;
1624  const auto e_thr = tuning::get_estimate_threshold();
1625  if (integer(size1) * size2 < integer(e_thr) * e_thr && this->m_n_threads == 1u) {
1626  estimate = false;
1627  }
1628  // If estimation is not worth it, we go with the plain multiplication.
1629  // NOTE: this is probably not optimal, but we have to do like this as the sparse
1630  // Kronecker multiplication below requires estimation. Maybe in the future we can
1631  // have a version without estimation.
1632  if (!estimate) {
1633  return this->plain_multiplication();
1634  }
1635  // Setup the return value.
1636  Series retval;
1637  retval.set_symbol_set(this->m_ss);
1638  // Do not do anything if one of the two series is empty, just return an empty series.
1639  if (unlikely(!size1 || !size2)) {
1640  return retval;
1641  }
1642  // Rehash the retun value's container accordingly. Check the tuning flag to see if we want to use
1643  // multiple threads for initing the return value.
1644  // NOTE: it is important here that we use the same n_threads for multiplication and memset as
1645  // we tie together pinned threads with potentially different NUMA regions.
1646  const unsigned n_threads_rehash = tuning::get_parallel_memory_set() ? this->m_n_threads : 1u;
1647  // Use the plain functor in normal mode for the estimation.
1648  const auto est
1649  = this->template estimate_final_series_size<1u, typename base::template plain_multiplier<false>>();
1650  // NOTE: if something goes wrong here, no big deal as retval is still empty.
1651  retval._container().rehash(boost::numeric_cast<typename Series::size_type>(
1652  std::ceil(static_cast<double>(est) / retval._container().max_load_factor())),
1653  n_threads_rehash);
1654  piranha_assert(retval._container().bucket_count());
1655  sparse_kronecker_multiplication(retval);
1656  return retval;
1657  }
1658  void sparse_kronecker_multiplication(Series &retval) const
1659  {
1660  using bucket_size_type = typename base::bucket_size_type;
1661  using size_type = typename base::size_type;
1662  using term_type = typename Series::term_type;
1663  // Type representing multiplication tasks:
1664  // - the current term index from s1,
1665  // - the first term index in s2,
1666  // - the last term index in s2.
1667  using task_type = std::tuple<size_type, size_type, size_type>;
1668  // Cache a few quantities.
1669  auto &v1 = this->m_v1;
1670  auto &v2 = this->m_v2;
1671  const auto size1 = v1.size();
1672  const auto size2 = v2.size();
1673  auto &container = retval._container();
1674  // A convenience functor to compute the destination bucket
1675  // of a term into retval.
1676  auto r_bucket = [&container](term_type const *p) { return container._bucket_from_hash(p->hash()); };
1677  // Sort input terms according to bucket positions in retval.
1678  auto term_cmp = [&r_bucket](term_type const *p1, term_type const *p2) { return r_bucket(p1) < r_bucket(p2); };
1679  std::stable_sort(v1.begin(), v1.end(), term_cmp);
1680  std::stable_sort(v2.begin(), v2.end(), term_cmp);
1681  // Task comparator. It will compare the bucket index of the terms resulting from
1682  // the multiplication of the term in the first series by the first term in the block
1683  // of the second series. This is essentially the first bucket index of retval in which the task
1684  // will write.
1685  // NOTE: this is guaranteed not to overflow as the max bucket size in the hash set is 2**(nbits-1),
1686  // and the max value of bucket_size_type is 2**nbits - 1.
1687  auto task_cmp = [&r_bucket, &v1, &v2](const task_type &t1, const task_type &t2) {
1688  return r_bucket(v1[std::get<0u>(t1)]) + r_bucket(v2[std::get<1u>(t1)])
1689  < r_bucket(v1[std::get<0u>(t2)]) + r_bucket(v2[std::get<1u>(t2)]);
1690  };
1691  // Task block size.
1692  const size_type block_size = safe_cast<size_type>(tuning::get_multiplication_block_size());
1693  // Task splitter: split a task in block_size sized tasks and append them to out.
1694  auto task_split = [block_size](const task_type &t, std::vector<task_type> &out) {
1695  size_type start = std::get<1u>(t), end = std::get<2u>(t);
1696  while (static_cast<size_type>(end - start) > block_size) {
1697  out.emplace_back(std::get<0u>(t), start, static_cast<size_type>(start + block_size));
1698  start = static_cast<size_type>(start + block_size);
1699  }
1700  if (end != start) {
1701  out.emplace_back(std::get<0u>(t), start, end);
1702  }
1703  };
1704  // End of the container, always the same value.
1705  const auto it_end = container.end();
1706  // Function to perform all the term-by-term multiplications in a task, using tmp_term
1707  // as a temporary value for the computation of the result.
1708  auto task_consume = [&v1, &v2, &container, it_end, this](const task_type &task, term_type &tmp_term) {
1709  // Get the term in the first series.
1710  term_type const *t1 = v1[std::get<0u>(task)];
1711  // Get pointers to the second series.
1712  term_type const **start2 = &(v2[std::get<1u>(task)]), **end2 = &(v2[std::get<2u>(task)]);
1713  // NOTE: these will have to be adapted for kd_monomial.
1714  using int_type = decltype(t1->m_key.get_int());
1715  // Get shortcuts to cf and key in t1.
1716  const auto &cf1 = t1->m_cf;
1717  const int_type key1 = t1->m_key.get_int();
1718  // Iterate over the task.
1719  for (; start2 != end2; ++start2) {
1720  // Const ref to the current term in the second series.
1721  const auto &cur = **start2;
1722  // Add the keys.
1723  // NOTE: this will have to be adapted for kd_monomial.
1724  tmp_term.m_key.set_int(static_cast<int_type>(key1 + cur.m_key.get_int()));
1725  // Try to locate the term into retval.
1726  auto bucket_idx = container._bucket(tmp_term);
1727  const auto it = container._find(tmp_term, bucket_idx);
1728  if (it == it_end) {
1729  // NOTE: for coefficient series, we might want to insert with move() below,
1730  // as we are not going to re-use the allocated resources in tmp.m_cf.
1731  // Take care of multiplying the coefficient.
1732  cf_mult_impl(tmp_term.m_cf, cf1, cur.m_cf);
1733  container._unique_insert(tmp_term, bucket_idx);
1734  } else {
1735  // NOTE: here we need to decide if we want to give the same treatment to fmp as we did with
1736  // cf_mult_impl.
1737  // For the moment it is an implementation detail of this class.
1738  this->fma_wrap(it->m_cf, cf1, cur.m_cf);
1739  }
1740  }
1741  };
1742  if (this->m_n_threads == 1u) {
1743  try {
1744  // Single threaded case.
1745  // Create the vector of tasks.
1746  std::vector<task_type> tasks;
1747  for (decltype(v1.size()) i = 0u; i < size1; ++i) {
1748  task_split(std::make_tuple(i, size_type(0u), size2), tasks);
1749  }
1750  // Sort the tasks.
1751  std::stable_sort(tasks.begin(), tasks.end(), task_cmp);
1752  // Iterate over the tasks and run the multiplication.
1753  term_type tmp_term;
1754  for (const auto &t : tasks) {
1755  task_consume(t, tmp_term);
1756  }
1757  this->sanitise_series(retval, this->m_n_threads);
1758  this->finalise_series(retval);
1759  } catch (...) {
1760  retval._container().clear();
1761  throw;
1762  }
1763  return;
1764  }
1765  // Number of buckets in retval.
1766  const bucket_size_type bucket_count = container.bucket_count();
1767  // Compute the number of zones in which the output container will be subdivided,
1768  // a multiple of the number of threads.
1769  // NOTE: zm is a tuning parameter.
1770  const unsigned zm = 10u;
1771  const bucket_size_type n_zones = static_cast<bucket_size_type>(integer(this->m_n_threads) * zm);
1772  // Number of buckets per zone (can be zero).
1773  const bucket_size_type bpz = static_cast<bucket_size_type>(bucket_count / n_zones);
1774  // For each zone, we need to define a vector of tasks that will write only into that zone.
1775  std::vector<std::vector<task_type>> task_table;
1776  task_table.resize(safe_cast<decltype(task_table.size())>(n_zones));
1777  // Lower bound implementation. Adapted from:
1778  // http://en.cppreference.com/w/cpp/algorithm/lower_bound
1779  // Given the [first,last[ index range in v2, find the first index idx in the v2 range such that the i-th
1780  // term in v1 multiplied by the idx-th term in v2 will be written into retval at a bucket index not less than
1781  // zb.
1782  auto l_bound
1783  = [&v1, &v2, &r_bucket](size_type first, size_type last, bucket_size_type zb, size_type i) -> size_type {
1784  piranha_assert(first <= last);
1785  bucket_size_type ib = r_bucket(v1[i]);
1786  // Avoid zb - ib below wrapping around.
1787  if (zb < ib) {
1788  return 0u;
1789  }
1790  const auto cmp = static_cast<bucket_size_type>(zb - ib);
1791  size_type idx, step, count = static_cast<size_type>(last - first);
1792  while (count > 0u) {
1793  idx = first;
1794  step = static_cast<size_type>(count / 2u);
1795  idx = static_cast<size_type>(idx + step);
1796  if (r_bucket(v2[idx]) < cmp) {
1797  idx = static_cast<size_type>(idx + 1u);
1798  first = idx;
1799  if (count <= step + 1u) {
1800  break;
1801  }
1802  count = static_cast<size_type>(count - (step + 1u));
1803  } else {
1804  count = step;
1805  }
1806  }
1807  return first;
1808  };
1809  // Fill the task table.
1810  auto table_filler = [&task_table, bpz, this, bucket_count, size1, size2, &l_bound, &task_split,
1811  &task_cmp](const unsigned &thread_idx) {
1812  for (unsigned n = 0u; n < zm; ++n) {
1813  std::vector<task_type> cur_tasks;
1814  // [a,b[ is the container zone.
1815  bucket_size_type a = static_cast<bucket_size_type>(thread_idx * bpz * zm + n * bpz);
1816  bucket_size_type b;
1817  if (n == zm - 1u && thread_idx == this->m_n_threads - 1u) {
1818  // Special casing if this is the last zone in the container.
1819  b = bucket_count;
1820  } else {
1821  b = static_cast<bucket_size_type>(a + bpz);
1822  }
1823  // First batch of tasks.
1824  for (size_type i = 0u; i < size1; ++i) {
1825  auto t = std::make_tuple(i, l_bound(0u, size2, a, i), l_bound(0u, size2, b, i));
1826  if (std::get<1u>(t) == 0u && std::get<2u>(t) == 0u) {
1827  // This means that all the next tasks we will compute will be empty,
1828  // no sense in calculating them.
1829  break;
1830  }
1831  task_split(t, cur_tasks);
1832  }
1833  // Second batch of tasks.
1834  // Note: we can always compute a,b + bucket_count because of the limits on the maximum value of
1835  // bucket_count.
1836  for (size_type i = 0u; i < size1; ++i) {
1837  auto t = std::make_tuple(i, l_bound(0u, size2, static_cast<bucket_size_type>(a + bucket_count), i),
1838  l_bound(0u, size2, static_cast<bucket_size_type>(b + bucket_count), i));
1839  if (std::get<1u>(t) == 0u && std::get<2u>(t) == 0u) {
1840  break;
1841  }
1842  task_split(t, cur_tasks);
1843  }
1844  // Sort the task vector.
1845  std::stable_sort(cur_tasks.begin(), cur_tasks.end(), task_cmp);
1846  // Move the vector of tasks in the table.
1847  task_table[static_cast<decltype(task_table.size())>(thread_idx * zm + n)] = std::move(cur_tasks);
1848  }
1849  };
1850  // Go with the threads to fill the task table.
1851  future_list<decltype(table_filler(0u))> ff_list;
1852  try {
1853  for (unsigned i = 0u; i < this->m_n_threads; ++i) {
1854  ff_list.push_back(thread_pool::enqueue(i, table_filler, i));
1855  }
1856  // First let's wait for everything to finish.
1857  ff_list.wait_all();
1858  // Then, let's handle the exceptions.
1859  ff_list.get_all();
1860  } catch (...) {
1861  ff_list.wait_all();
1862  throw;
1863  }
1864  // Check the consistency of the table for debug purposes.
1865  auto table_checker = [&task_table, size1, size2, &r_bucket, bpz, bucket_count, &v1, &v2]() -> bool {
1866  // Total number of term-by-term multiplications. Needs to be equal
1867  // to size1 * size2 at the end.
1868  integer tot_n(0);
1869  // Tmp term for multiplications.
1870  term_type tmp_term;
1871  for (decltype(task_table.size()) i = 0u; i < task_table.size(); ++i) {
1872  const auto &v = task_table[i];
1873  // Bucket limits of each zone.
1874  bucket_size_type a = static_cast<bucket_size_type>(bpz * i), b;
1875  // Special casing for the last zone in the table.
1876  if (i == task_table.size() - 1u) {
1877  b = bucket_count;
1878  } else {
1879  b = static_cast<bucket_size_type>(a + bpz);
1880  }
1881  for (const auto &t : v) {
1882  auto idx1 = std::get<0u>(t), start2 = std::get<1u>(t), end2 = std::get<2u>(t);
1883  using int_type = decltype(v1[idx1]->m_key.get_int());
1884  piranha_assert(start2 <= end2);
1885  tot_n += end2 - start2;
1886  for (; start2 != end2; ++start2) {
1887  tmp_term.m_key.set_int(
1888  static_cast<int_type>(v1[idx1]->m_key.get_int() + v2[start2]->m_key.get_int()));
1889  auto b_idx = r_bucket(&tmp_term);
1890  if (b_idx < a || b_idx >= b) {
1891  return false;
1892  }
1893  }
1894  }
1895  }
1896  return tot_n == integer(size1) * size2;
1897  };
1898  (void)table_checker;
1899  piranha_assert(table_checker());
1900  // Init the vector of atomic flags.
1901  detail::atomic_flag_array af(safe_cast<std::size_t>(task_table.size()));
1902  // Thread functor.
1903  auto thread_functor = [&task_table, &af, &task_consume](const unsigned &thread_idx) {
1904  using t_size_type = decltype(task_table.size());
1905  // Temporary term_type for caching.
1906  term_type tmp_term;
1907  // The starting index in the task table.
1908  auto t_idx = static_cast<t_size_type>(t_size_type(thread_idx) * zm);
1909  const auto start_t_idx = t_idx;
1910  while (true) {
1911  // If this returns false, it means that the tasks still need to be consumed;
1912  if (!af[static_cast<std::size_t>(t_idx)].test_and_set()) {
1913  // Current vector of tasks.
1914  const auto &cur_tasks = task_table[t_idx];
1915  for (const auto &t : cur_tasks) {
1916  task_consume(t, tmp_term);
1917  }
1918  }
1919  // Update the index, wrapping around if necessary.
1920  t_idx = static_cast<t_size_type>(t_idx + 1u);
1921  if (t_idx == task_table.size()) {
1922  t_idx = 0u;
1923  }
1924  // If we got back to the original index, get out.
1925  if (t_idx == start_t_idx) {
1926  break;
1927  }
1928  }
1929  };
1930  // Go with the multiplication threads.
1931  future_list<decltype(thread_functor(0u))> ft_list;
1932  try {
1933  for (unsigned i = 0u; i < this->m_n_threads; ++i) {
1934  ft_list.push_back(thread_pool::enqueue(i, thread_functor, i));
1935  }
1936  // First let's wait for everything to finish.
1937  ft_list.wait_all();
1938  // Then, let's handle the exceptions.
1939  ft_list.get_all();
1940  // Finally, fix and finalise the series.
1941  this->sanitise_series(retval, this->m_n_threads);
1942  this->finalise_series(retval);
1943  } catch (...) {
1944  ft_list.wait_all();
1945  // Clean up and re-throw.
1946  retval._container().clear();
1947  throw;
1948  }
1949  }
1950 };
1951 }
1952 
1953 #endif
size_type size() const
Series size.
Definition: series.hpp:2095
math_pow_t< T, U > pow(const T &x, const U &y)
Exponentiation.
Definition: pow.hpp:126
~polynomial()
Trivial destructor.
Definition: polynomial.hpp:486
Cf m_cf
Coefficient member.
Definition: term.hpp:189
void wait_all()
Wait on all the futures.
inverse_type< Series > invert() const
Inversion.
Definition: polynomial.hpp:557
Type trait for multipliable key.
detail::math_integrate_type< T > integrate(const T &x, const std::string &str)
Integration.
Definition: math.hpp:743
Multiprecision integer class.
Definition: mp++.hpp:869
Poisson series class.
mp_integer< 1 > integer
Alias for piranha::mp_integer with 1 limb of static storage.
Definition: mp_integer.hpp:63
Equality-comparable type trait.
detail::math_partial_type< T > partial(const T &x, const std::string &str)
Partial derivative.
Definition: math.hpp:691
polynomial(Str &&name)
Constructor from symbol name.
Definition: polynomial.hpp:480
Type trait to detect coefficient types.
Definition: is_cf.hpp:55
static polynomial untruncated_multiplication(const polynomial &p1, const polynomial &p2)
Untruncated multiplication.
Definition: polynomial.hpp:800
Polynomial class.
Definition: polynomial.hpp:149
const_iterator find(const key_type &k) const
Find element.
Definition: hash_set.hpp:963
Trigonometric series toolbox.
symbol_idx_fset ss_intersect_idx(const symbol_fset &s1, const symbol_fset &s2)
Find the indices of the intersection of two symbol_fset.
In-place addable type trait.
Exceptions.
const_iterator end() const
Const end iterator.
Definition: hash_set.hpp:883
void negate(T &x)
In-place negation.
Definition: math.hpp:329
STL namespace.
#define PIRANHA_FORWARDING_CTOR(Derived, Base)
Constructor-forwarding macro.
Definition: forwarding.hpp:50
void get_all()
Get all the futures.
Key m_key
Key member.
Definition: term.hpp:191
static enqueue_t< F &&, Args &&... > enqueue(unsigned n, F &&f, Args &&... args)
Enqueue task.
auto degree(const T &x) -> decltype(degree_impl< T >()(x))
Total degree.
Definition: math.hpp:1341
static std::tuple< int, degree_type< T >, symbol_fset > get_auto_truncate_degree()
Query the status of the degree-based auto-truncation mechanism.
Definition: polynomial.hpp:727
static void clear_pow_cache()
Clear the internal cache of natural powers.
Definition: series.hpp:2371
Type trait to detect the presence of the piranha::math::is_zero() function.
Definition: math.hpp:1049
partial_type< Series > partial(const std::string &name) const
Partial derivative.
Definition: series.hpp:2405
integrate_type< T > integrate(const std::string &name) const
Integration.
Definition: polynomial.hpp:591
Forwarding macros.
static polynomial truncated_multiplication(const polynomial &p1, const polynomial &p2, const U &max_degree, const symbol_fset &names)
Truncated multiplication (partial degree).
Definition: polynomial.hpp:872
typename v_ptr::size_type size_type
The size type of base_series_multiplier::v_ptr.
Term class.
Definition: term.hpp:66
#define piranha_throw(exception_type,...)
Exception-throwing macro.
Definition: exceptions.hpp:118
boost::container::flat_set< std::string > symbol_fset
Flat set of symbols.
Key key_type
Alias for the key type.
Definition: term.hpp:79
Type trait to detect if types can be used in piranha::math::truncate_degree().
Definition: math.hpp:1739
polynomial & operator=(const polynomial &other)=default
Copy assignment operator.
const_iterator begin() const
Const begin iterator.
Definition: hash_set.hpp:859
Cf cf_type
Alias for the coefficient type.
Definition: term.hpp:77
Series _truncated_multiplication(const T &max_degree, const Args &... args) const
Truncated multiplication.
symbol_idx ss_index_of(const symbol_fset &ref, const std::string &name)
Identify the index of a symbol in a symbol_fset.
bool is_zero(const T &x)
Zero test.
Definition: math.hpp:133
Class to store a list of futures.
Toolbox for substitutable series.
term< Cf, Key > term_type
Alias for term type.
Definition: series.hpp:1266
const_iterator begin() const
Begin iterator.
Definition: series.hpp:2504
static unsigned long get_estimate_threshold()
Get the series estimation threshold.
Definition: tuning.hpp:159
Series class.
Definition: series_fwd.hpp:45
Root piranha namespace.
Definition: array_key.hpp:52
Type trait to detect the availability of piranha::math::multiply_accumulate().
Definition: math.hpp:2383
Type traits.
Type trait for integrable types.
Definition: math.hpp:1830
void multiply_accumulate(T &x, const T &y, const T &z)
Multiply-accumulate.
Definition: math.hpp:438
const_iterator end() const
End iterator.
Definition: series.hpp:2523
static polynomial truncated_multiplication(const polynomial &p1, const polynomial &p2, const U &max_degree)
Truncated multiplication (total degree).
Definition: polynomial.hpp:835
Cf find_cf(const T &c) const
Find coefficient.
Definition: polynomial.hpp:754
static void unset_auto_truncate_degree()
Disable degree-based auto-truncation.
Definition: polynomial.hpp:701
static void set_auto_truncate_degree(const U &max_degree)
Set total-degree-based auto-truncation.
Definition: polynomial.hpp:637
Detect the availability of std::begin() and std::end().
static unsigned long get_multiplication_block_size()
Get the multiplication block size.
Definition: tuning.hpp:117
static bool get_parallel_memory_set()
Get the parallel_memory_set flag.
Definition: tuning.hpp:81
boost::container::flat_set< symbol_idx > symbol_idx_fset
Flat set of symbol indices.
Power series toolbox.
Type trait to detect piranha::safe_cast().
Definition: safe_cast.hpp:237
Toolbox for series suitable for integral power substitution.
polynomial()=default
Defaulted default constructor.
series_multiplier(const Series &s1, const Series &s2)
Constructor.
std::vector< typename base::size_type > _get_skip_limits(const std::vector< T > &v_d1, const std::vector< T > &v_d2, const T &max_degree) const
Establish skip limits for truncated multiplication.
void set_symbol_set(const symbol_fset &args)
Symbol set setter.
Definition: series.hpp:2773
container_type m_container
Terms container.
Definition: series.hpp:2805
#define PIRANHA_TT_CHECK(tt,...)
Macro for static type trait checks.
Type trait to detect series types.
Definition: series_fwd.hpp:49
static void set_auto_truncate_degree(const U &max_degree, const symbol_fset &names)
Set partial-degree-based auto-truncation.
Definition: polynomial.hpp:676
pow_ret_type< T > pow(const T &x) const
Override default exponentiation method.
Definition: polynomial.hpp:530
#define PIRANHA_FORWARDING_ASSIGNMENT(Derived, Base)
Assignment-forwarding macro.
Definition: forwarding.hpp:68
std::vector< typename Series::term_type const * > v_ptr
Alias for a vector of const pointers to series terms.
Exception to signal failure in piranha::safe_cast().
Definition: safe_cast.hpp:53
safe_cast_type< To, From > safe_cast(const From &x)
Safe cast.
Definition: safe_cast.hpp:219
void push_back(std::future< T > &&f)
Move-insert a future.
Cf find_cf(std::initializer_list< T > l) const
Find coefficient.
Definition: polynomial.hpp:774