piranha  0.10
series.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_SERIES_HPP
30 #define PIRANHA_SERIES_HPP
31 
32 #include <algorithm>
33 #include <array>
34 #include <boost/iostreams/copy.hpp>
35 #include <boost/iostreams/filter/bzip2.hpp>
36 #include <boost/iostreams/filtering_streambuf.hpp>
37 #include <boost/iterator/indirect_iterator.hpp>
38 #include <boost/iterator/transform_iterator.hpp>
39 #include <boost/numeric/conversion/cast.hpp>
40 #include <cmath>
41 #include <cstddef>
42 #include <cstdint>
43 #include <fstream>
44 #include <functional>
45 #include <ios>
46 #include <iostream>
47 #include <iterator>
48 #include <limits>
49 #include <memory>
50 #include <mutex>
51 #include <sstream>
52 #include <stdexcept>
53 #include <string>
54 #include <tuple>
55 #include <type_traits>
56 #include <unordered_map>
57 #include <utility>
58 #include <vector>
59 
60 #include <piranha/config.hpp>
61 #include <piranha/convert_to.hpp>
62 #include <piranha/debug_access.hpp>
63 #include <piranha/detail/init_data.hpp>
64 #include <piranha/detail/series_fwd.hpp>
65 #include <piranha/detail/sfinae_types.hpp>
66 #include <piranha/exceptions.hpp>
67 #include <piranha/hash_set.hpp>
68 #include <piranha/invert.hpp>
69 #include <piranha/is_cf.hpp>
70 #include <piranha/key_is_convertible.hpp>
71 #include <piranha/math.hpp>
72 #include <piranha/mp_integer.hpp>
73 #include <piranha/pow.hpp>
74 #include <piranha/print_coefficient.hpp>
75 #include <piranha/print_tex_coefficient.hpp>
76 #include <piranha/s11n.hpp>
77 #include <piranha/safe_cast.hpp>
78 #include <piranha/series_multiplier.hpp>
79 #include <piranha/settings.hpp>
80 #include <piranha/symbol_utils.hpp>
81 #include <piranha/term.hpp>
82 #include <piranha/type_traits.hpp>
83 
84 namespace piranha
85 {
86 
87 inline namespace impl
88 {
89 
90 // Fwd declaration.
91 template <typename S1, typename S2, typename F>
92 auto series_merge_f(S1 &&s1, S2 &&s2, const F &f) -> decltype(f(std::forward<S1>(s1), std::forward<S2>(s2)));
93 }
94 
95 namespace detail
96 {
97 
98 // NOTE: this needs to go here, instead of in the series class as private method,
99 // because of a bug in GCC 4.7/4.8:
100 // http://gcc.gnu.org/bugzilla/show_bug.cgi?id=53137
101 template <typename Term, typename Derived>
102 inline std::pair<typename Term::cf_type, Derived> pair_from_term(const symbol_fset &s, const Term &t)
103 {
104  typedef typename Term::cf_type cf_type;
105  Derived retval;
106  retval.set_symbol_set(s);
107  retval.insert(Term(cf_type(1), t.m_key));
108  return std::make_pair(t.m_cf, std::move(retval));
109 }
110 
111 // Apply a functor to a single-coefficient series. This is in the spirit of the old apply_cf_functor method,
112 // and it is used in some of the math:: overrides below.
113 template <typename Functor, typename RetT, typename T>
114 inline RetT apply_cf_functor(const T &s)
115 {
116  using term_type = typename RetT::term_type;
117  using cf_type = typename term_type::cf_type;
118  using key_type = typename term_type::key_type;
119  if (!s.is_single_coefficient()) {
120  piranha_throw(std::invalid_argument,
121  std::string("cannot compute ") + Functor::name + ", series is not single-coefficient");
122  }
123  RetT retval;
124  Functor f;
125  if (s.empty()) {
126  retval.insert(term_type(f(cf_type(0)), key_type(symbol_fset{})));
127  } else {
128  retval.insert(term_type(f(s._container().begin()->m_cf), key_type(symbol_fset{})));
129  }
130  return retval;
131 }
132 }
133 
135 
138 // NOTE: runtime/other requirements:
139 // - a def cted series is empty (this is used, e.g., in the series multiplier);
140 // - typedefs in series should not be overridden.
141 template <typename T>
142 class is_series
143 {
144  static const bool implementation_defined
145  = conjunction<std::is_base_of<detail::series_tag, T>, is_container_element<T>>::value;
146 
147 public:
149  static const bool value = implementation_defined;
150 };
151 
152 template <typename T>
153 const bool is_series<T>::value;
154 
155 // Implementation of the series_is_rebindable tt.
156 inline namespace impl
157 {
158 
159 template <typename T, typename Cf>
160 using series_rebind_t = typename uncvref_t<T>::template rebind<uncvref_t<Cf>>;
161 
162 template <typename T, typename Cf>
163 using series_rebind_cf_t = typename series_rebind_t<T, Cf>::term_type::cf_type;
164 
165 template <typename T, typename Cf, typename = void>
166 struct series_is_rebindable_impl {
167  static const bool value = conjunction<is_series<detected_t<series_rebind_t, T, Cf>>,
168  std::is_same<detected_t<series_rebind_cf_t, T, Cf>, uncvref_t<Cf>>>::value;
169 };
170 
171 template <typename T, typename Cf>
172 struct series_is_rebindable_impl<
173  T, Cf, enable_if_t<disjunction<negation<is_cf<uncvref_t<Cf>>>, negation<is_series<uncvref_t<T>>>>::value>> {
174  static const bool value = false;
175 };
176 }
177 
179 
192 // NOTE: the behaviour when T and Cf do not satisfy the requirements is to allow this type trait
193 // to be used an all types. The trait is used in the metaprogramming of generic series arithmetics,
194 // and if we ever move the operators outside the series_operators class it is convenient to have this
195 // class not fire static asserts when non-series arguments are substituted.
196 template <typename T, typename Cf>
198 private:
199  static const bool implementation_defined = series_is_rebindable_impl<T, Cf>::value;
200 
201 public:
203  static const bool value = implementation_defined;
204 };
205 
206 template <typename T, typename Cf>
208 
209 inline namespace impl
210 {
211 
212 // Implementation of the series_rebind alias, with SFINAE to soft-disable it in case
213 // the series is not rebindable.
214 template <typename T, typename Cf>
215 using series_rebind_implementation = enable_if_t<series_is_rebindable<T, Cf>::value, series_rebind_t<T, Cf>>;
216 }
217 
219 
224 template <typename T, typename Cf>
225 using series_rebind = series_rebind_implementation<T, Cf>;
226 
228 
236 template <typename T, typename = void>
238 {
239  static const std::size_t implementation_defined = 0u;
240 
241 public:
243  static const std::size_t value = implementation_defined;
244 };
245 
246 template <typename T, typename Enable>
248 
249 #if !defined(PIRANHA_DOXYGEN_INVOKED)
250 
251 template <typename T>
253  T, typename std::enable_if<std::is_base_of<detail::series_tag, typename std::decay<T>::type>::value>::type>
254 {
255  using cf_type = typename std::decay<T>::type::term_type::cf_type;
256  static_assert(series_recursion_index<cf_type>::value < std::numeric_limits<std::size_t>::max(), "Overflow error.");
257 
258 public:
259  static const std::size_t value = static_cast<std::size_t>(series_recursion_index<cf_type>::value + 1u);
260 };
261 
262 template <typename T>
263 const std::size_t series_recursion_index<
264  T, typename std::enable_if<std::is_base_of<detail::series_tag, typename std::decay<T>::type>::value>::type>::value;
265 
266 #endif
267 
269 
274 template <typename Series>
275 class series_has_multiplier : detail::sfinae_types
276 {
277  using Sd = typename std::decay<Series>::type;
278  PIRANHA_TT_CHECK(is_series, Sd);
279  template <typename T>
280  static auto test(const T &s) -> decltype(series_multiplier<T>(s, s)());
281  static no test(...);
282  static const bool implementation_defined = std::is_same<decltype(test(std::declval<Sd>())), Sd>::value;
283 
284 public:
286  static const bool value = implementation_defined;
287 };
288 
289 template <typename Series>
291 
292 namespace detail
293 {
294 
295 // Some notes on this machinery:
296 // - this is only for determining the type of the result, but it does not guarantee that we can actually compute it.
297 // In general we should separate the algorithmic requirements from the determination of the type. Note that we still
298 // use the operators on the coefficients to determine the return type, but that's inevitable.
299 
300 // Alias for getting the cf type from a series. Will generate a type error if S is not a series.
301 // NOTE: the is_series check is an extra safe guard to really assert we are
302 // operating on series. Without the checks, in theory classes with internal term_type and similar typedefs
303 // could trigger no errors.
304 template <typename S>
305 using bso_cf_t = typename std::enable_if<is_series<S>::value, typename S::term_type::cf_type>::type;
306 
307 // Result of a generic arithmetic binary operation:
308 // - 0 for addition,
309 // - 1 for subtraction,
310 // - 2 for multiplication,
311 // - 3 for division.
312 // No type is defined if the operation is not supported by the involved types.
313 template <typename, typename, int, typename = void>
314 struct op_result {
315 };
316 
317 template <typename T, typename U>
318 struct op_result<T, U, 0, typename std::enable_if<is_addable<T, U>::value>::type> {
319  using type = decltype(std::declval<const T &>() + std::declval<const U &>());
320 };
321 
322 template <typename T, typename U>
323 struct op_result<T, U, 1, typename std::enable_if<is_subtractable<T, U>::value>::type> {
324  using type = decltype(std::declval<const T &>() - std::declval<const U &>());
325 };
326 
327 template <typename T, typename U>
328 struct op_result<T, U, 2, typename std::enable_if<is_multipliable<T, U>::value>::type> {
329  using type = decltype(std::declval<const T &>() * std::declval<const U &>());
330 };
331 
332 template <typename T, typename U>
333 struct op_result<T, U, 3, typename std::enable_if<is_divisible<T, U>::value>::type> {
334  using type = decltype(std::declval<const T &>() / std::declval<const U &>());
335 };
336 
337 // Coefficient type in a binary arithmetic operation between two series with the same recursion index.
338 // Will generate a type error if S1 or S2 are not series with same recursion index or if their coefficients
339 // do not support the operation.
340 template <typename S1, typename S2, int N>
341 using bso_cf_op_t = typename std::enable_if<series_recursion_index<S1>::value == series_recursion_index<S2>::value
343  typename op_result<bso_cf_t<S1>, bso_cf_t<S2>, N>::type>::type;
344 
345 // Coefficient type in a mixed binary arithmetic operation in which the first operand has recursion index
346 // greater than the second. Will generate a type error if S does not have a rec. index > T, or if the coefficient
347 // of S cannot be opped to T.
348 template <typename S, typename T, int N>
349 using bsom_cf_op_t = typename std::enable_if<(series_recursion_index<S>::value > series_recursion_index<T>::value),
350  typename op_result<bso_cf_t<S>, T, N>::type>::type;
351 
352 // Default case is empty for SFINAE.
353 template <typename, typename, int, typename = void>
354 struct binary_series_op_return_type {
355 };
356 
357 // Case 0:
358 // a. both are series with same recursion index,
359 // b. the coefficients are the same and the op results in the same coefficient,
360 // c. the two series types are equal.
361 template <typename S1, typename S2, int N>
362 struct binary_series_op_return_type<S1, S2, N,
363  typename std::enable_if<
364  // NOTE: in all these checks, every access to S1 and S2 should not incur in hard
365  // errors:
366  // - the series recursion index works on all types safely,
367  // - the various _t aliases are SFINAE friendly - if S1 or S2 are not series
368  // with the appropriate characteristics,
369  // or the coefficients are not oppable, etc., there should be a soft error.
370  /*a*/ series_recursion_index<S1>::value == series_recursion_index<S2>::value
371  && series_recursion_index<S1>::value != 0u &&
372  /*b*/ std::is_same<bso_cf_op_t<S1, S2, N>, bso_cf_t<S1>>::value
373  && std::is_same<bso_cf_op_t<S1, S2, N>, bso_cf_t<S2>>::value &&
374  /*c*/ std::is_same<S1, S2>::value>::type> {
375  using type = S1;
376  static const unsigned value = 0u;
377 };
378 
379 // Case 1:
380 // a. both are series with same recursion index,
381 // b. the coefficients are not the same and the op results in the first coefficient.
382 template <typename S1, typename S2, int N>
383 struct binary_series_op_return_type<S1, S2, N,
384  typename std::enable_if<
385  /*a*/ series_recursion_index<S1>::value == series_recursion_index<S2>::value
386  && series_recursion_index<S1>::value != 0u &&
387  /*b*/ std::is_same<bso_cf_op_t<S1, S2, N>, bso_cf_t<S1>>::value
388  && !std::is_same<bso_cf_t<S1>, bso_cf_t<S2>>::value>::type> {
389  using type = S1;
390  static const unsigned value = 1u;
391 };
392 
393 // Case 2:
394 // a. both are series with same recursion index,
395 // b. the coefficients are not the same and the op results in the second coefficient.
396 template <typename S1, typename S2, int N>
397 struct binary_series_op_return_type<S1, S2, N,
398  typename std::enable_if<
399  /*a*/ series_recursion_index<S1>::value == series_recursion_index<S2>::value
400  && series_recursion_index<S1>::value != 0u &&
401  /*b*/ std::is_same<bso_cf_op_t<S1, S2, N>, bso_cf_t<S2>>::value
402  && !std::is_same<bso_cf_t<S1>, bso_cf_t<S2>>::value>::type> {
403  using type = S2;
404  static const unsigned value = 2u;
405 };
406 
407 // Case 3:
408 // a. both are series with same recursion index,
409 // b. the coefficient op results in something else than first or second cf,
410 // c. both series are rebindable to the new coefficient, yielding the same series.
411 template <typename S1, typename S2, int N>
412 struct binary_series_op_return_type<S1, S2, N,
413  typename std::enable_if<
414  /*a*/ series_recursion_index<S1>::value == series_recursion_index<S2>::value
415  && series_recursion_index<S1>::value != 0u &&
416  /*b*/ !std::is_same<bso_cf_op_t<S1, S2, N>, bso_cf_t<S1>>::value
417  && !std::is_same<bso_cf_op_t<S1, S2, N>, bso_cf_t<S2>>::value
418  &&
419  /*c*/ std::is_same<series_rebind<S1, bso_cf_op_t<S1, S2, N>>,
420  series_rebind<S2, bso_cf_op_t<S1, S2, N>>>::value>::type> {
421  using type = series_rebind<S1, bso_cf_op_t<S1, S2, N>>;
422  static const unsigned value = 3u;
423 };
424 
425 // Case 4:
426 // a. the first operand has recursion index greater than the second operand,
427 // b. the op of the coefficient of S1 by S2 results in the coefficient of S1.
428 template <typename S1, typename S2, int N>
429 struct binary_series_op_return_type<S1, S2, N,
430  typename std::enable_if<
431  /*a*/ (series_recursion_index<S1>::value > series_recursion_index<S2>::value) &&
432  /*b*/ std::is_same<bsom_cf_op_t<S1, S2, N>, bso_cf_t<S1>>::value>::type> {
433  using type = S1;
434  static const unsigned value = 4u;
435 };
436 
437 // Case 5:
438 // a. the first operand has recursion index greater than the second operand,
439 // b. the op of the coefficient of S1 by S2 results in a type T different from the coefficient of S1,
440 // c. S1 is rebindable to T.
441 template <typename S1, typename S2, int N>
442 struct binary_series_op_return_type<S1, S2, N,
443  typename std::enable_if<
444  /*a*/ (series_recursion_index<S1>::value > series_recursion_index<S2>::value) &&
445  /*b*/ !std::is_same<bsom_cf_op_t<S1, S2, N>, bso_cf_t<S1>>::value &&
446  /*c*/ series_is_rebindable<S1, bsom_cf_op_t<S1, S2, N>>::value>::type> {
447  using type = series_rebind<S1, bsom_cf_op_t<S1, S2, N>>;
448  static const unsigned value = 5u;
449 };
450 
451 // Case 6 (symmetric of 4):
452 // a. the second operand has recursion index greater than the first operand,
453 // b. the op of the coefficient of S2 by S1 results in the coefficient of S2.
454 template <typename S1, typename S2, int N>
455 struct binary_series_op_return_type<S1, S2, N,
456  typename std::enable_if<
457  /*a*/ (series_recursion_index<S2>::value > series_recursion_index<S1>::value) &&
458  /*b*/ std::is_same<bsom_cf_op_t<S2, S1, N>, bso_cf_t<S2>>::value>::type> {
459  using type = S2;
460  static const unsigned value = 6u;
461 };
462 
463 // Case 7 (symmetric of 5):
464 // a. the second operand has recursion index greater than the first operand,
465 // b. the op of the coefficient of S2 by S1 results in a type T different from the coefficient of S2,
466 // c. S2 is rebindable to T.
467 template <typename S1, typename S2, int N>
468 struct binary_series_op_return_type<S1, S2, N,
469  typename std::enable_if<
470  /*a*/ (series_recursion_index<S2>::value > series_recursion_index<S1>::value) &&
471  /*b*/ !std::is_same<bsom_cf_op_t<S2, S1, N>, bso_cf_t<S2>>::value &&
472  /*c*/ series_is_rebindable<S2, bsom_cf_op_t<S2, S1, N>>::value>::type> {
473  using type = series_rebind<S2, bsom_cf_op_t<S2, S1, N>>;
474  static const unsigned value = 7u;
475 };
476 }
477 
479 
540 {
541  // A couple of handy aliases.
542  // NOTE: we need both bso_type and common_type because bso gives access to the ::value
543  // member to decide on the implementation, common_type is handy because it's shorter
544  // than using ::type on the bso.
545  template <typename T, typename U, int N>
546  using bso_type
547  = detail::binary_series_op_return_type<typename std::decay<T>::type, typename std::decay<U>::type, N>;
548  template <typename T, typename U, int N>
549  using series_common_type = typename bso_type<T, U, N>::type;
550  // Main implementation function for add/sub: all the possible cases end up here eventually, after
551  // any necessary conversion.
552  template <bool Sign, typename T, typename U>
553  static series_common_type<T, U, 0> binary_add_impl(T &&x, U &&y)
554  {
555  // This is the same as T and U.
556  using ret_type = series_common_type<T, U, 0>;
557  static_assert(std::is_same<typename std::decay<T>::type, ret_type>::value, "Invalid type.");
558  static_assert(std::is_same<typename std::decay<U>::type, ret_type>::value, "Invalid type.");
559  // Init the return value from the first operand.
560  // NOTE: this is always possible to do, a series is always copy/move-constructible.
561  ret_type retval(std::forward<T>(x));
562  // NOTE: here we cover the corner case in which x and y are the same object.
563  // This could be problematic in the algorithm below if retval was move-cted from
564  // x, since in such a case y will also be erased. This will happen if one does
565  // std::move(a) + std::move(a).
566  if (unlikely(&x == &y)) {
567  retval.template merge_terms<Sign>(retval);
568  return retval;
569  }
570  // NOTE: we do not use x any more, as it might have been moved.
571  if (likely(retval.m_symbol_set == y.m_symbol_set)) {
572  retval.template merge_terms<Sign>(std::forward<U>(y));
573  } else {
574  // Let's fix the args of the first series, if needed.
575  const auto merge = ss_merge(retval.m_symbol_set, y.m_symbol_set);
576  if (std::get<0>(merge) != retval.m_symbol_set) {
577  // This is a move assignment, always possible.
578  retval = retval.merge_arguments(std::get<0>(merge), std::get<1>(merge));
579  }
580  // Fix the args of the second series.
581  if (std::get<0>(merge) != y.m_symbol_set) {
582  retval.template merge_terms<Sign>(y.merge_arguments(std::get<0>(merge), std::get<2>(merge)));
583  } else {
584  retval.template merge_terms<Sign>(std::forward<U>(y));
585  }
586  }
587  return retval;
588  }
589  // NOTE: this case has no special algorithmic requirements, the base requirements for cf and series types
590  // already cover everything (including the needeed series constructors and cf arithmetic operators).
591  template <typename T, typename U, typename std::enable_if<bso_type<T, U, 0>::value == 0u, int>::type = 0>
592  static series_common_type<T, U, 0> dispatch_binary_add(T &&x, U &&y)
593  {
594  return binary_add_impl<true>(std::forward<T>(x), std::forward<U>(y));
595  }
596  // NOTE: this covers two cases:
597  // - equal recursion, first series wins,
598  // - first higher recursion, coefficient of first series wins,
599  // In both cases we need to construct a T from y.
600  template <typename T, typename U,
601  typename std::enable_if<
602  (bso_type<T, U, 0>::value == 1u || bso_type<T, U, 0>::value == 4u) &&
603  // In order for the implementation to work, we need to be able to build T from U.
604  std::is_constructible<typename std::decay<T>::type, const typename std::decay<U>::type &>::value,
605  int>::type
606  = 0>
607  static series_common_type<T, U, 0> dispatch_binary_add(T &&x, U &&y)
608  {
609  typename std::decay<T>::type y1(std::forward<U>(y));
610  return dispatch_binary_add(std::forward<T>(x), std::move(y1));
611  }
612  // Symmetric of 1.
613  template <typename T, typename U, typename std::enable_if<bso_type<T, U, 0>::value == 2u, int>::type = 0>
614  static auto dispatch_binary_add(T &&x, U &&y)
615  -> decltype(dispatch_binary_add(std::forward<U>(y), std::forward<T>(x)))
616  {
617  return dispatch_binary_add(std::forward<U>(y), std::forward<T>(x));
618  }
619  // Two cases:
620  // - equal series recursion, 3rd coefficient type generated,
621  // - first higher recursion, coefficient result is different from first series.
622  // In both cases we need to promote both operands to a 3rd type.
623  template <
624  typename T, typename U,
625  typename std::enable_if<
626  (bso_type<T, U, 0>::value == 3u || bso_type<T, U, 0>::value == 5u) &&
627  // In order for the implementation to work, we need to be able to build the
628  // common type from T and U.
629  std::is_constructible<series_common_type<T, U, 0>, const typename std::decay<T>::type &>::value
630  && std::is_constructible<series_common_type<T, U, 0>, const typename std::decay<U>::type &>::value,
631  int>::type
632  = 0>
633  static series_common_type<T, U, 0> dispatch_binary_add(T &&x, U &&y)
634  {
635  series_common_type<T, U, 0> x1(std::forward<T>(x));
636  series_common_type<T, U, 0> y1(std::forward<U>(y));
637  return dispatch_binary_add(std::move(x1), std::move(y1));
638  }
639  // 6 and 7 are symmetric cases of 4 and 5. Just reverse the operands.
640  template <typename T, typename U,
641  typename std::enable_if<bso_type<T, U, 0>::value == 6u || bso_type<T, U, 0>::value == 7u, int>::type = 0>
642  static auto dispatch_binary_add(T &&x, U &&y)
643  -> decltype(dispatch_binary_add(std::forward<U>(y), std::forward<T>(x)))
644  {
645  return dispatch_binary_add(std::forward<U>(y), std::forward<T>(x));
646  }
647  // NOTE: here we use the version of binary_add with const references. The idea
648  // here is that any overload other than the const references one is an optimisation detail
649  // and that for the operator to be enabled the "canonical" form of addition operator must be available.
650  // The consequence is that if, for instance, only a move constructor is available, the operator
651  // will anyway be disabled even if technically we could still perform the computation.
652  template <typename T, typename... U>
653  using binary_add_type = decltype(dispatch_binary_add(std::declval<const typename std::decay<T>::type &>(),
654  std::declval<const typename std::decay<U>::type &>()...));
655  // In-place add. Default implementation is to do simply x = x + y, if possible.
656  // NOTE: this should also be able to handle int += series, if we ever implement it.
657  template <typename T, typename U,
658  typename std::enable_if<std::is_assignable<T &, binary_add_type<T, U>>::value, int>::type = 0>
659  static T &dispatch_in_place_add(T &x, U &&y)
660  {
661  // NOTE: we can move x here, as it is going to be re-assigned anyway.
662  x = dispatch_binary_add(std::move(x), std::forward<U>(y));
663  return x;
664  }
665  // NOTE: as explained above, here the enabling mechanism essentially recasts U as a const reference,
666  // because we require in-place addition to work with the second argument as a const reference (as that is
667  // the canonical form of in-place addition). The actual call will however do perfect forwarding, so optimisations
668  // are still possible.
669  template <typename T, typename... U>
670  using in_place_add_type
671  = decltype(dispatch_in_place_add(std::declval<T &>(), std::declval<const typename std::decay<U>::type &>()...));
672  template <typename T, typename... U>
673  using in_place_add_enabler = typename std::enable_if<detail::true_tt<in_place_add_type<T, U...>>::value, int>::type;
674  // Subtraction.
675  template <typename T, typename U, typename std::enable_if<bso_type<T, U, 1>::value == 0u, int>::type = 0>
676  static series_common_type<T, U, 1> dispatch_binary_sub(T &&x, U &&y)
677  {
678  return binary_add_impl<false>(std::forward<T>(x), std::forward<U>(y));
679  }
680  template <typename T, typename U,
681  typename std::enable_if<(bso_type<T, U, 1>::value == 1u || bso_type<T, U, 1>::value == 4u)
682  && std::is_constructible<typename std::decay<T>::type,
683  const typename std::decay<U>::type &>::value,
684  int>::type
685  = 0>
686  static series_common_type<T, U, 1> dispatch_binary_sub(T &&x, U &&y)
687  {
688  typename std::decay<T>::type y1(std::forward<U>(y));
689  return dispatch_binary_sub(std::forward<T>(x), std::move(y1));
690  }
691  template <typename T, typename U, typename std::enable_if<bso_type<T, U, 1>::value == 2u, int>::type = 0>
692  static auto dispatch_binary_sub(T &&x, U &&y)
693  -> decltype(dispatch_binary_sub(std::forward<U>(y), std::forward<T>(x)))
694  {
695  auto retval = dispatch_binary_sub(std::forward<U>(y), std::forward<T>(x));
696  retval.negate();
697  return retval;
698  }
699  template <
700  typename T, typename U,
701  typename std::enable_if<
702  (bso_type<T, U, 1>::value == 3u || bso_type<T, U, 1>::value == 5u)
703  && std::is_constructible<series_common_type<T, U, 1>, const typename std::decay<T>::type &>::value
704  && std::is_constructible<series_common_type<T, U, 1>, const typename std::decay<U>::type &>::value,
705  int>::type
706  = 0>
707  static series_common_type<T, U, 1> dispatch_binary_sub(T &&x, U &&y)
708  {
709  series_common_type<T, U, 1> x1(std::forward<T>(x));
710  series_common_type<T, U, 1> y1(std::forward<U>(y));
711  return dispatch_binary_sub(std::move(x1), std::move(y1));
712  }
713  template <typename T, typename U,
714  typename std::enable_if<bso_type<T, U, 1>::value == 6u || bso_type<T, U, 1>::value == 7u, int>::type = 0>
715  static auto dispatch_binary_sub(T &&x, U &&y)
716  -> decltype(dispatch_binary_sub(std::forward<U>(y), std::forward<T>(x)))
717  {
718  auto retval = dispatch_binary_sub(std::forward<U>(y), std::forward<T>(x));
719  retval.negate();
720  return retval;
721  }
722  template <typename T, typename... U>
723  using binary_sub_type = decltype(dispatch_binary_sub(std::declval<const typename std::decay<T>::type &>(),
724  std::declval<const typename std::decay<U>::type &>()...));
725  template <typename T, typename U,
726  typename std::enable_if<std::is_assignable<T &, binary_sub_type<T, U>>::value, int>::type = 0>
727  static T &dispatch_in_place_sub(T &x, U &&y)
728  {
729  x = dispatch_binary_sub(std::move(x), std::forward<U>(y));
730  return x;
731  }
732  template <typename T, typename... U>
733  using in_place_sub_type
734  = decltype(dispatch_in_place_sub(std::declval<T &>(), std::declval<const typename std::decay<U>::type &>()...));
735  template <typename T, typename... U>
736  using in_place_sub_enabler = typename std::enable_if<detail::true_tt<in_place_sub_type<T, U...>>::value, int>::type;
737  // Multiplication.
738  struct binary_mul_impl {
739  template <typename T, typename U>
740  series_common_type<T, U, 2> operator()(T &&x, U &&y) const
741  {
742  return series_multiplier<series_common_type<T, U, 2>>(std::forward<T>(x), std::forward<U>(y))();
743  }
744  };
745  template <typename T, typename U, typename std::enable_if<bso_type<T, U, 2>::value == 0u, int>::type = 0>
746  static series_common_type<T, U, 2> dispatch_binary_mul(T &&x, U &&y)
747  {
748  using ret_type = series_common_type<T, U, 2>;
749  static_assert(std::is_same<typename std::decay<T>::type, ret_type>::value, "Invalid type.");
750  static_assert(std::is_same<typename std::decay<U>::type, ret_type>::value, "Invalid type.");
751  return series_merge_f(std::forward<T>(x), std::forward<U>(y), binary_mul_impl{});
752  }
753  template <typename T, typename U,
754  typename std::enable_if<(bso_type<T, U, 2>::value == 1u || bso_type<T, U, 2>::value == 4u)
755  && std::is_constructible<typename std::decay<T>::type,
756  const typename std::decay<U>::type &>::value,
757  int>::type
758  = 0>
759  static series_common_type<T, U, 2> dispatch_binary_mul(T &&x, U &&y)
760  {
761  typename std::decay<T>::type y1(std::forward<U>(y));
762  return dispatch_binary_mul(std::forward<T>(x), std::move(y1));
763  }
764  template <typename T, typename U, typename std::enable_if<bso_type<T, U, 2>::value == 2u, int>::type = 0>
765  static auto dispatch_binary_mul(T &&x, U &&y)
766  -> decltype(dispatch_binary_mul(std::forward<U>(y), std::forward<T>(x)))
767  {
768  return dispatch_binary_mul(std::forward<U>(y), std::forward<T>(x));
769  }
770  template <
771  typename T, typename U,
772  typename std::enable_if<
773  (bso_type<T, U, 2>::value == 3u || bso_type<T, U, 2>::value == 5u)
774  && std::is_constructible<series_common_type<T, U, 2>, const typename std::decay<T>::type &>::value
775  && std::is_constructible<series_common_type<T, U, 2>, const typename std::decay<U>::type &>::value,
776  int>::type
777  = 0>
778  static series_common_type<T, U, 2> dispatch_binary_mul(T &&x, U &&y)
779  {
780  series_common_type<T, U, 2> x1(std::forward<T>(x));
781  series_common_type<T, U, 2> y1(std::forward<U>(y));
782  return dispatch_binary_mul(std::move(x1), std::move(y1));
783  }
784  template <typename T, typename U,
785  typename std::enable_if<bso_type<T, U, 2>::value == 6u || bso_type<T, U, 2>::value == 7u, int>::type = 0>
786  static auto dispatch_binary_mul(T &&x, U &&y)
787  -> decltype(dispatch_binary_mul(std::forward<U>(y), std::forward<T>(x)))
788  {
789  return dispatch_binary_mul(std::forward<U>(y), std::forward<T>(x));
790  }
791  // NOTE: this is the real type from the multiplication, below we put another enable_if to make it conditional
792  // on the presence of a series multiplier.
793  template <typename T, typename... U>
794  using binary_mul_type_ = decltype(dispatch_binary_mul(std::declval<const typename std::decay<T>::type &>(),
795  std::declval<const typename std::decay<U>::type &>()...));
796  template <typename T, typename... U>
797  using binary_mul_type = typename std::enable_if<series_has_multiplier<binary_mul_type_<T, U...>>::value,
798  binary_mul_type_<T, U...>>::type;
799  template <typename T, typename U,
800  typename std::enable_if<std::is_assignable<T &, binary_mul_type<T, U>>::value, int>::type = 0>
801  static T &dispatch_in_place_mul(T &x, U &&y)
802  {
803  x = dispatch_binary_mul(std::move(x), std::forward<U>(y));
804  return x;
805  }
806  template <typename T, typename... U>
807  using in_place_mul_type
808  = decltype(dispatch_in_place_mul(std::declval<T &>(), std::declval<const typename std::decay<U>::type &>()...));
809  template <typename T, typename... U>
810  using in_place_mul_enabler = typename std::enable_if<detail::true_tt<in_place_mul_type<T, U...>>::value, int>::type;
811  // Division.
812  // NOTE: the divisibility requirement here is already satisfied in the determination of the return type.
813  // The base case: same recursion, no promotion.
814  template <typename T, typename U, typename std::enable_if<bso_type<T, U, 3>::value == 0u, int>::type = 0>
815  static series_common_type<T, U, 3> dispatch_binary_div(T &&x, U &&y)
816  {
817  using ret_type = series_common_type<T, U, 3>;
818  using term_type = typename ret_type::term_type;
819  using cf_type = typename term_type::cf_type;
820  using key_type = typename term_type::key_type;
821  if (!y.is_single_coefficient()) {
822  piranha_throw(std::invalid_argument, "divisor in series division does not consist of a single coefficient");
823  }
824  const auto y_cf = y.empty() ? cf_type(0) : y._container().begin()->m_cf;
825  if (x.empty()) {
826  ret_type retval;
827  retval.insert(term_type{cf_type{0} / y_cf, key_type{retval.get_symbol_set()}});
828  return retval;
829  }
830  return x / y_cf;
831  }
832  // Same recursion, first coefficient wins.
833  template <typename T, typename U,
834  typename std::enable_if<bso_type<T, U, 3>::value == 1u
835  && std::is_constructible<typename std::decay<T>::type,
836  const typename std::decay<U>::type &>::value,
837  int>::type
838  = 0>
839  static series_common_type<T, U, 3> dispatch_binary_div(T &&x, U &&y)
840  {
841  typename std::decay<T>::type y1(std::forward<U>(y));
842  return dispatch_binary_div(std::forward<T>(x), std::move(y1));
843  }
844  // Two cases:
845  // - same recursion, second coefficient wins,
846  // - second operand has higher recursion, first operand promotes to second.
847  template <typename T, typename U,
848  typename std::enable_if<(bso_type<T, U, 3>::value == 2u || bso_type<T, U, 3>::value == 6u)
849  && std::is_constructible<typename std::decay<U>::type,
850  const typename std::decay<T>::type &>::value,
851  int>::type
852  = 0>
853  static series_common_type<T, U, 3> dispatch_binary_div(T &&x, U &&y)
854  {
855  typename std::decay<U>::type x1(std::forward<T>(x));
856  return dispatch_binary_div(std::move(x1), std::forward<U>(y));
857  }
858  // Two cases:
859  // - same recursion, need promoted coefficient,
860  // - second operand has higher recursion, both promote to a third type.
861  template <
862  typename T, typename U,
863  typename std::enable_if<
864  (bso_type<T, U, 3>::value == 3u || bso_type<T, U, 3>::value == 7u)
865  && std::is_constructible<series_common_type<T, U, 3>, const typename std::decay<T>::type &>::value
866  && std::is_constructible<series_common_type<T, U, 3>, const typename std::decay<U>::type &>::value,
867  int>::type
868  = 0>
869  static series_common_type<T, U, 3> dispatch_binary_div(T &&x, U &&y)
870  {
871  series_common_type<T, U, 0> x1(std::forward<T>(x));
872  series_common_type<T, U, 0> y1(std::forward<U>(y));
873  return dispatch_binary_div(std::move(x1), std::move(y1));
874  }
875  // The implementation of these two cases 4 and 5 is different from the other operations, as we cannot promote to a
876  // common type (true series division is not implemented).
877  // NOTE: the use of the old syntax for the enable_if with nullptr is because of a likely GCC bug:
878  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59366
879  // In real.hpp, there are a few uses of operator/= on reals (e.g., in binary_div) *before* the is_zero_impl
880  // specialisation for real is declared. These operators are immediately instantiated when parsed because they are
881  // not templated. During the overload resolution of operator/=, the operator defined in this class is considered -
882  // which is wrong, as the operators here should be found only via ADL and this class is in no way associated to
883  // real. What happens then is that is_zero_impl is instantiated for a real type before the real specialisation is
884  // seen, and GCC errors out. I *think* the nullptr syntax works because the bso_type enable_if disables the function
885  // before has_is_zero is encountered, or maybe because it does not participate in template deduction.
886  template <typename T, typename U, typename std::enable_if<bso_type<T, U, 3>::value == 4u, int>::type = 0>
887  static series_common_type<T, U, 3>
888  dispatch_binary_div(T &&x, U &&y,
889  typename std::enable_if<has_is_zero<typename std::decay<U>::type>::value>::type * = nullptr)
890  {
891  using ret_type = series_common_type<T, U, 3>;
892  if (x.empty()) {
893  // Special case: if x is empty, then we will just insert a single term consisting of 0 / y.
894  using term_type = typename ret_type::term_type;
895  using cf_type = typename term_type::cf_type;
896  using key_type = typename term_type::key_type;
897  ret_type retval;
898  retval.insert(term_type{cf_type{0} / y, key_type{retval.get_symbol_set()}});
899  return retval;
900  }
901  static_assert(std::is_same<typename std::decay<T>::type, ret_type>::value, "Invalid type.");
902  // Create a copy of x and work on it. This is always possible.
903  ret_type retval(std::forward<T>(x));
904  // NOTE: x is not used any more.
905  const auto it_f = retval.m_container.end();
906  try {
907  for (auto it = retval.m_container.begin(); it != it_f;) {
908  // NOTE: here the original requirement is that cf / y is defined, but we know
909  // that cf / y results in another cf, and we assume always that cf /= y is exactly equivalent
910  // to cf = cf / y. And cf must be move-assignable. So this should be possible.
911  it->m_cf /= y;
912  // NOTE: no need to check for compatibility, as it depends only on the key type and here
913  // we are only acting on the coefficient.
914  if (unlikely(it->is_zero(retval.m_symbol_set))) {
915  // Erase will return the next iterator.
916  it = retval.m_container.erase(it);
917  } else {
918  ++it;
919  }
920  }
921  } catch (...) {
922  // In case of errors clear out the series.
923  retval.m_container.clear();
924  throw;
925  }
926  return retval;
927  }
928  // NOTE: the trailing decltype() syntax is used here to make sure we can actually call the other overload of the
929  // function
930  // in the body.
931  template <typename T, typename U,
932  typename std::enable_if<bso_type<T, U, 3>::value == 5u
933  && std::is_constructible<series_common_type<T, U, 3>,
934  const typename std::decay<T>::type &>::value,
935  int>::type
936  = 0>
937  static auto dispatch_binary_div(T &&x, U &&y)
938  -> decltype(dispatch_binary_div(std::declval<const series_common_type<T, U, 3> &>(), std::forward<U>(y)))
939  {
940  series_common_type<T, U, 3> x1(std::forward<T>(x));
941  return dispatch_binary_div(std::move(x1), std::forward<U>(y));
942  }
943  template <typename T, typename... U>
944  using binary_div_type = decltype(dispatch_binary_div(std::declval<const typename std::decay<T>::type &>(),
945  std::declval<const typename std::decay<U>::type &>()...));
946  template <typename T, typename U,
947  typename std::enable_if<std::is_assignable<T &, binary_div_type<T, U>>::value, int>::type = 0>
948  static T &dispatch_in_place_div(T &x, U &&y)
949  {
950  x = dispatch_binary_div(std::move(x), std::forward<U>(y));
951  return x;
952  }
953  template <typename T, typename... U>
954  using in_place_div_type
955  = decltype(dispatch_in_place_div(std::declval<T &>(), std::declval<const typename std::decay<U>::type &>()...));
956  template <typename T, typename... U>
957  using in_place_div_enabler = typename std::enable_if<detail::true_tt<in_place_div_type<T, U...>>::value, int>::type;
958  // Equality.
959  // Low-level implementation of equality.
960  template <typename T>
961  static bool equality_impl(const T &x, const T &y)
962  {
963  if (x.size() != y.size()) {
964  return false;
965  }
966  // NOTE: maybe it's possible to write an optimised algorithm
967  // in case the sizes coincide: compare bucket by bucket (e.g.,
968  // if the buckets at a certain index in the two operands have
969  // different sizes, then the series are different). Maybe this could
970  // even be moved into hash_set to be its equality operator.
971  piranha_assert(x.m_symbol_set == y.m_symbol_set);
972  const auto it_f_x = x.m_container.end(), it_f_y = y.m_container.end();
973  for (auto it = x.m_container.begin(); it != it_f_x; ++it) {
974  const auto tmp_it = y.m_container.find(*it);
975  if (tmp_it == it_f_y || tmp_it->m_cf != it->m_cf) {
976  return false;
977  }
978  }
979  return true;
980  }
981  // NOTE: we are using operator+() on the coefficients to determine type conversions.
982  template <typename T, typename U, typename std::enable_if<bso_type<T, U, 0>::value == 0u, int>::type = 0>
983  static bool dispatch_equality(const T &x, const U &y)
984  {
985  static_assert(std::is_same<T, U>::value, "Invalid types for the equality operator.");
986  return series_merge_f(x, y, [](const T &a, const T &b) { return series_operators::equality_impl(a, b); });
987  }
988  template <typename T, typename U,
989  typename std::enable_if<(bso_type<T, U, 0>::value == 1u || bso_type<T, U, 0>::value == 4u)
990  && std::is_constructible<T, const U &>::value,
991  int>::type
992  = 0>
993  static bool dispatch_equality(const T &x, const U &y)
994  {
995  return dispatch_equality(x, T(y));
996  }
997  template <typename T, typename U, typename std::enable_if<bso_type<T, U, 0>::value == 2u, int>::type = 0>
998  static auto dispatch_equality(const T &x, const U &y) -> decltype(dispatch_equality(y, x))
999  {
1000  return dispatch_equality(y, x);
1001  }
1002  template <typename T, typename U,
1003  typename std::enable_if<(bso_type<T, U, 0>::value == 3u || bso_type<T, U, 0>::value == 5u)
1004  && std::is_constructible<series_common_type<T, U, 0>, const T &>::value
1005  && std::is_constructible<series_common_type<T, U, 0>, const U &>::value,
1006  int>::type
1007  = 0>
1008  static bool dispatch_equality(const T &x, const U &y)
1009  {
1010  series_common_type<T, U, 0> x1(x);
1011  series_common_type<T, U, 0> y1(y);
1012  return dispatch_equality(x1, y1);
1013  }
1014  template <typename T, typename U,
1015  typename std::enable_if<bso_type<T, U, 0>::value == 6u || bso_type<T, U, 0>::value == 7u, int>::type = 0>
1016  static auto dispatch_equality(const T &x, const U &y) -> decltype(dispatch_equality(y, x))
1017  {
1018  return dispatch_equality(y, x);
1019  }
1020  template <typename T, typename... U>
1021  using eq_type = decltype(dispatch_equality(std::declval<const typename std::decay<T>::type &>(),
1022  std::declval<const typename std::decay<U>::type &>()...));
1023  template <typename T, typename... U>
1024  using eq_enabler = typename std::enable_if<detail::true_tt<eq_type<T, U...>>::value, int>::type;
1025 
1026 public:
1028 
1043  template <typename T, typename... U>
1044  friend binary_add_type<T, U...> operator+(T &&x, U &&... y)
1045  {
1046  return dispatch_binary_add(std::forward<T>(x), std::forward<U>(y)...);
1047  }
1049 
1061  template <typename T, typename... U, in_place_add_enabler<T, U...> = 0>
1062  friend T &operator+=(T &x, U &&... y)
1063  {
1064  return dispatch_in_place_add(x, std::forward<U>(y)...);
1065  }
1067 
1083  template <typename T, typename... U>
1084  friend binary_sub_type<T, U...> operator-(T &&x, U &&... y)
1085  {
1086  return dispatch_binary_sub(std::forward<T>(x), std::forward<U>(y)...);
1087  }
1089 
1101  template <typename T, typename... U, in_place_sub_enabler<T, U...> = 0>
1102  friend T &operator-=(T &x, U &&... y)
1103  {
1104  return dispatch_in_place_sub(x, std::forward<U>(y)...);
1105  }
1107 
1123  template <typename T, typename... U>
1124  friend binary_mul_type<T, U...> operator*(T &&x, U &&... y)
1125  {
1126  return dispatch_binary_mul(std::forward<T>(x), std::forward<U>(y)...);
1127  }
1129 
1141  template <typename T, typename... U, in_place_mul_enabler<T, U...> = 0>
1142  friend T &operator*=(T &x, U &&... y)
1143  {
1144  return dispatch_in_place_mul(x, std::forward<U>(y)...);
1145  }
1147 
1163  template <typename T, typename... U>
1164  friend binary_div_type<T, U...> operator/(T &&x, U &&... y)
1165  {
1166  return dispatch_binary_div(std::forward<T>(x), std::forward<U>(y)...);
1167  }
1169 
1181  template <typename T, typename... U, in_place_div_enabler<T, U...> = 0>
1182  friend T &operator/=(T &x, U &&... y)
1183  {
1184  return dispatch_in_place_div(x, std::forward<U>(y)...);
1185  }
1187 
1206  template <typename T, typename... U, eq_enabler<T, U...> = 0>
1207  friend bool operator==(const T &x, const U &... y)
1208  {
1209  return dispatch_equality(x, y...);
1210  }
1212 
1224  template <typename T, typename... U, eq_enabler<T, U...> = 0>
1225  friend bool operator!=(const T &x, const U &... y)
1226  {
1227  return !dispatch_equality(x, y...);
1228  }
1229 };
1230 
1232 
1251 /* TODO:
1252  * \todo cast operator, to series and non-series types.
1253  * \todo cast operator would allow to define in-place operators with fundamental types as first operand.
1254  * \todo filter and transform can probably take arbitrary functors as input, instead of std::function. Just assert the
1255  * function object's signature.
1256  * \todo transform needs sfinaeing.
1257  * TODO new operators:
1258  * - test with mock_cfs that are not addable to scalars.
1259  * - the unary + and - operators should probably follow the type promotion rules for consistency.
1260  */
1261 template <typename Cf, typename Key, typename Derived>
1262 class series : detail::series_tag, series_operators
1263 {
1264 public:
1267 
1268 private:
1269  // Make friend with all series.
1270  template <typename, typename, typename>
1271  friend class series;
1272  // Make friend with debugging class.
1273  template <typename>
1274  friend class debug_access;
1275  // Make friend with the operators class.
1276  friend class series_operators;
1277  // Partial need access to the custom derivatives.
1278  template <typename, typename>
1279  friend struct math::partial_impl;
1280 #if !defined(PIRANHA_DOXYGEN_INVOKED)
1281  // Friendship with the series_merge_f helper.
1282  template <typename S1, typename S2, typename F>
1283  friend auto impl::series_merge_f(S1 &&s1, S2 &&s2, const F &f)
1284  -> decltype(f(std::forward<S1>(s1), std::forward<S2>(s2)));
1285 #endif
1286 
1287 protected:
1290 
1291 private:
1292 #if !defined(PIRANHA_DOXYGEN_INVOKED)
1293  // Avoid confusing doxygen.
1294  typedef decltype(std::declval<container_type>().evaluate_sparsity()) sparsity_info_type;
1295  // Insertion.
1296  template <bool Sign, typename T>
1297  void dispatch_insertion(
1298  T &&term,
1299  typename std::enable_if<std::is_same<typename std::decay<T>::type, term_type>::value>::type * = nullptr)
1300  {
1301  // Debug checks.
1302  piranha_assert(empty() || m_container.begin()->is_compatible(m_symbol_set));
1303  // Generate error if term is not compatible.
1304  if (unlikely(!term.is_compatible(m_symbol_set))) {
1305  piranha_throw(std::invalid_argument, "cannot insert incompatible term");
1306  }
1307  // Discard ignorable term.
1308  if (unlikely(term.is_zero(m_symbol_set))) {
1309  return;
1310  }
1311  insertion_impl<Sign>(std::forward<T>(term));
1312  }
1313  // Cf arithmetics when inserting, normal and move variants.
1314  template <bool Sign, typename Iterator>
1315  static void insertion_cf_arithmetics(Iterator &it, const term_type &term)
1316  {
1317  if (Sign) {
1318  it->m_cf += term.m_cf;
1319  } else {
1320  it->m_cf -= term.m_cf;
1321  }
1322  }
1323  template <bool Sign, typename Iterator>
1324  static void insertion_cf_arithmetics(Iterator &it, term_type &&term)
1325  {
1326  if (Sign) {
1327  it->m_cf += std::move(term.m_cf);
1328  } else {
1329  it->m_cf -= std::move(term.m_cf);
1330  }
1331  }
1332  // Insert compatible, non-ignorable term.
1333  template <bool Sign, typename T>
1334  void insertion_impl(T &&term)
1335  {
1336  // NOTE: here we are basically going to reconstruct hash_set::insert() with the goal
1337  // of optimising things by avoiding one branch.
1338  // Handle the case of a table with no buckets.
1339  if (unlikely(!m_container.bucket_count())) {
1340  m_container._increase_size();
1341  }
1342  // Try to locate the element.
1343  auto bucket_idx = m_container._bucket(term);
1344  const auto it = m_container._find(term, bucket_idx);
1345  // Cleanup function that checks ignorability of an element in the hash set,
1346  // and removes it if necessary.
1347  auto cleanup = [this](const typename container_type::const_iterator &it_c) {
1348  if (unlikely(it_c->is_zero(this->m_symbol_set))) {
1349  this->m_container.erase(it_c);
1350  }
1351  };
1352  if (it == m_container.end()) {
1353  if (unlikely(m_container.size() == std::numeric_limits<size_type>::max())) {
1354  piranha_throw(std::overflow_error, "maximum number of elements reached");
1355  }
1356  // Term is new. Handle the case in which we need to rehash because of load factor.
1357  if (unlikely(static_cast<double>(m_container.size() + size_type(1u))
1358  / static_cast<double>(m_container.bucket_count())
1359  > m_container.max_load_factor())) {
1360  m_container._increase_size();
1361  // We need a new bucket index in case of a rehash.
1362  bucket_idx = m_container._bucket(term);
1363  }
1364  const auto new_it = m_container._unique_insert(std::forward<T>(term), bucket_idx);
1365  m_container._update_size(m_container.size() + size_type(1u));
1366  // Insertion was successful, change sign if requested.
1367  if (!Sign) {
1368  try {
1369  math::negate(new_it->m_cf);
1370  cleanup(new_it);
1371  } catch (...) {
1372  // Run the cleanup function also in case of exceptions, as we do not know
1373  // in which state the modified term is.
1374  cleanup(new_it);
1375  throw;
1376  }
1377  }
1378  } else {
1379  // Assert the existing term is not ignorable and it is compatible.
1380  piranha_assert(!it->is_zero(m_symbol_set) && it->is_compatible(m_symbol_set));
1381  try {
1382  // The term exists already, update it.
1383  insertion_cf_arithmetics<Sign>(it, std::forward<T>(term));
1384  // Check if the term has become ignorable after the modification.
1385  cleanup(it);
1386  } catch (...) {
1387  cleanup(it);
1388  throw;
1389  }
1390  }
1391  }
1392  template <typename T>
1393  using insert_enabler =
1394  typename std::enable_if<std::is_same<term_type, typename std::decay<T>::type>::value, int>::type;
1395  // Terms merging
1396  // =============
1397  // NOTE: ideas to improve the algorithm:
1398  // - optimization when merging with self: add each coefficient to itself, instead of copying and merging.
1399  // - optimization when merging with series with same bucket size: avoid computing the destination bucket,
1400  // as it will be the same as the original.
1401  template <bool Sign, typename T>
1402  void merge_terms_impl0(T &&s)
1403  {
1404  // NOTE: here we can take the pointer to series and compare it to this because we know that
1405  // series derives from the type of this.
1406  if (unlikely(&s == this)) {
1407  // If the two series are the same object, we need to make a copy.
1408  // NOTE: here we are making sure we are doing a real deep copy (as opposed, say, to a move,
1409  // which could happen if we used std::forward.).
1410  merge_terms_impl1<Sign>(series<Cf, Key, Derived>(static_cast<const series<Cf, Key, Derived> &>(s)));
1411  } else {
1412  merge_terms_impl1<Sign>(std::forward<T>(s));
1413  }
1414  }
1415  // Overload if we cannot move objects from series.
1416  template <bool Sign, typename T>
1417  void merge_terms_impl1(T &&s, typename std::enable_if<!is_nonconst_rvalue_ref<T &&>::value>::type * = nullptr)
1418  {
1419  const auto it_f = s.m_container.end();
1420  try {
1421  for (auto it = s.m_container.begin(); it != it_f; ++it) {
1422  insert<Sign>(*it);
1423  }
1424  } catch (...) {
1425  // In case of any insertion error, zero out this series.
1426  m_container.clear();
1427  throw;
1428  }
1429  }
1430  static void swap_for_merge(container_type &&c1, container_type &&c2, bool &swap)
1431  {
1432  piranha_assert(!swap);
1433  // Do not do anything in case of overflows.
1434  if (unlikely(c1.size() > std::numeric_limits<size_type>::max() - c2.size())) {
1435  return;
1436  }
1437  // This is the maximum number of terms in the return value.
1438  const auto max_size = c1.size() + c2.size();
1439  // This will be the maximum required number of buckets.
1440  size_type max_n_buckets;
1441  try {
1442  piranha_assert(c1.max_load_factor() > 0);
1443  max_n_buckets
1444  = boost::numeric_cast<size_type>(std::trunc(static_cast<double>(max_size) / c1.max_load_factor()));
1445  } catch (...) {
1446  // Ignore any error on conversions.
1447  return;
1448  }
1449  // Now we swap the containers, if the first one is not enough to contain the expected number of terms
1450  // and if the second one is larger.
1451  if (c1.bucket_count() < max_n_buckets && c2.bucket_count() > c1.bucket_count()) {
1452  container_type tmp(std::move(c1));
1453  c1 = std::move(c2);
1454  c2 = std::move(tmp);
1455  swap = true;
1456  }
1457  }
1458  // This overloads the above in the case we are dealing with two different container types:
1459  // in such a condition, we won't do any swapping.
1460  template <typename OtherContainerType>
1461  static void swap_for_merge(container_type &&, OtherContainerType &&, bool &)
1462  {
1463  }
1464  // Overload if we can move objects from series.
1465  template <bool Sign, typename T>
1466  void merge_terms_impl1(T &&s, typename std::enable_if<is_nonconst_rvalue_ref<T &&>::value>::type * = nullptr)
1467  {
1468  bool swap = false;
1469  // Try to steal memory from other.
1470  swap_for_merge(std::move(m_container), std::move(s.m_container), swap);
1471  try {
1472  const auto it_f = s.m_container._m_end();
1473  for (auto it = s.m_container._m_begin(); it != it_f; ++it) {
1474  insert<Sign>(std::move(*it));
1475  }
1476  // If we swapped the operands and a negative merge was performed, we need to change
1477  // the signs of all coefficients.
1478  if (swap && !Sign) {
1479  const auto it_f2 = m_container.end();
1480  for (auto it = m_container.begin(); it != it_f2;) {
1481  math::negate(it->m_cf);
1482  if (unlikely(it->is_zero(m_symbol_set))) {
1483  // Erase the invalid term.
1484  it = m_container.erase(it);
1485  } else {
1486  ++it;
1487  }
1488  }
1489  }
1490  } catch (...) {
1491  // In case of any insertion error, zero out both series.
1492  m_container.clear();
1493  s.m_container.clear();
1494  throw;
1495  }
1496  // The other series must alway be cleared, since we moved out the terms.
1497  s.m_container.clear();
1498  }
1499  // Merge all terms from another series. Works if s is this (in which case a copy is made). Basic exception safety
1500  // guarantee.
1501  template <bool Sign, typename T>
1502  void merge_terms(T &&s, typename std::enable_if<is_series<typename std::decay<T>::type>::value>::type * = nullptr)
1503  {
1504  static_assert(std::is_base_of<series<Cf, Key, Derived>, typename std::decay<T>::type>::value, "Type error.");
1505  merge_terms_impl0<Sign>(std::forward<T>(s));
1506  }
1507  // Generic construction
1508  // ====================
1509  template <typename T, typename U = series,
1510  typename std::enable_if<(series_recursion_index<T>::value < series_recursion_index<U>::value)
1512  int>::type
1513  = 0>
1514  void dispatch_generic_constructor(const T &x)
1515  {
1516  typedef typename term_type::cf_type cf_type;
1517  typedef typename term_type::key_type key_type;
1518  cf_type cf(convert_to<cf_type>(x));
1519  key_type key(m_symbol_set);
1520  insert(term_type(std::move(cf), std::move(key)));
1521  }
1522  // NOTE: here in principle we could have a type U which is not a series but has a term_type typedef defined,
1523  // which could incur into a hard error (e.g., via a static_assert in U). Maybe for the future we could think of
1524  // defining
1525  // a term_type_t template alias in the spirit of series_rebind, that goes into SFINAE if the argument is not a
1526  // series.
1527  // Alternatively, the key_is_convertible and other similar typedefs with internal static asserts could be made
1528  // sfinae-friendly
1529  // in a similar way (i.e., don't define the ::value member if the input types are not keys).
1530  template <typename T, typename U = series,
1531  typename std::enable_if<
1536  int>::type
1537  = 0>
1538  void dispatch_generic_constructor(const T &s)
1539  {
1540  typedef typename term_type::cf_type cf_type;
1541  typedef typename term_type::key_type key_type;
1542  m_symbol_set = s.m_symbol_set;
1543  const auto it_f = s.m_container.end();
1544  try {
1545  for (auto it = s.m_container.begin(); it != it_f; ++it) {
1546  insert<true>(term_type(convert_to<cf_type>(it->m_cf), key_type(it->m_key, m_symbol_set)));
1547  }
1548  } catch (...) {
1549  // In case of any insertion error, zero out this series.
1550  m_container.clear();
1551  throw;
1552  }
1553  }
1554  // NOTE: here we need to make sure that the generic ctor cannot be preferred over the copy constructor,
1555  // otherwise we pay a performance penalty. We need to make sure of the following things:
1556  // - this must *not* be a copy constructor for series - this will prevent automatically-generated copy constructors
1557  // lower in the class hierarchy to call it;
1558  // - if we call explicitly a base() copy constructor lower in the hierarchy, the generic constructor must be
1559  // excluded
1560  // from the overload set.
1561  // Here, U is the calling series object, T the generic object. The is_base_of check makes sure of the above
1562  // conditions:
1563  // - if T is U exactly, then clearly std::is_base_of<U,T>::value is true;
1564  // - if T comes from lower in the hierarchy, then clearly std::is_base_of<U,T>::value is true again.
1565  // These aspects are tested in series_03.
1566  template <typename T, typename U>
1567  using generic_ctor_enabler = typename std::enable_if<
1568  detail::true_tt<decltype(std::declval<U &>().dispatch_generic_constructor(std::declval<const T &>()))>::value
1569  && !std::is_base_of<U, T>::value,
1570  int>::type;
1571  // Enabler for is_identical.
1572  template <typename T>
1573  using is_identical_enabler = typename std::enable_if<is_equality_comparable<T>::value, int>::type;
1574  // Iterator utilities.
1575  typedef boost::transform_iterator<std::function<std::pair<typename term_type::cf_type, Derived>(const term_type &)>,
1576  typename container_type::const_iterator>
1577  const_iterator_impl;
1578  // Print utilities.
1579  template <bool TexMode, typename Iterator>
1580  static std::ostream &print_helper(std::ostream &os, Iterator start, Iterator end, const symbol_fset &args)
1581  {
1582  piranha_assert(start != end);
1583  const auto limit = settings::get_max_term_output();
1584  size_type count = 0u;
1585  std::ostringstream oss;
1586  auto it = start;
1587  for (; it != end;) {
1588  if (limit && count == limit) {
1589  break;
1590  }
1591  std::ostringstream oss_cf;
1592  if (TexMode) {
1593  print_tex_coefficient(oss_cf, it->m_cf);
1594  } else {
1595  print_coefficient(oss_cf, it->m_cf);
1596  }
1597  auto str_cf = oss_cf.str();
1598  std::ostringstream oss_key;
1599  if (TexMode) {
1600  it->m_key.print_tex(oss_key, args);
1601  } else {
1602  it->m_key.print(oss_key, args);
1603  }
1604  auto str_key = oss_key.str();
1605  if (str_cf == "1" && !str_key.empty()) {
1606  str_cf = "";
1607  } else if (str_cf == "-1" && !str_key.empty()) {
1608  str_cf = "-";
1609  }
1610  oss << str_cf;
1611  if (str_cf != "" && str_cf != "-" && !str_key.empty() && !TexMode) {
1612  oss << "*";
1613  }
1614  oss << str_key;
1615  ++it;
1616  if (it != end) {
1617  oss << "+";
1618  }
1619  ++count;
1620  }
1621  auto str = oss.str();
1622  // If we reached the limit without printing all terms in the series, print the ellipsis.
1623  if (limit && count == limit && it != end) {
1624  if (TexMode) {
1625  str += "\\ldots";
1626  } else {
1627  str += "...";
1628  }
1629  }
1630  std::string::size_type index = 0u;
1631  while (true) {
1632  index = str.find("+-", index);
1633  if (index == std::string::npos) {
1634  break;
1635  }
1636  str.replace(index, 2u, "-");
1637  ++index;
1638  }
1639  os << str;
1640  return os;
1641  }
1642  // Merge arguments using a map m computed by ss_merge(). new_s is the new
1643  // merged symbol set.
1644  Derived merge_arguments(const symbol_fset &new_s, const symbol_idx_fmap<symbol_fset> &m) const
1645  {
1646  // We should never invoke this with an empty insertion map.
1647  piranha_assert(m.size());
1648  // The last index of the insertion map must be at most the size
1649  // of the current symbol set (which means that symbols need to be
1650  // appended).
1651  piranha_assert(m.rbegin()->first <= m_symbol_set.size());
1652 #if !defined(NDEBUG)
1653  // In debug mode, verify that the concatenation of the values
1654  // in m forms a strictly monotonic sequence.
1655  auto verify_ins_map = [&m]() -> bool {
1656  std::vector<std::string> v_str;
1657  for (const auto &p : m) {
1658  v_str.insert(v_str.end(), p.second.begin(), p.second.end());
1659  }
1660  return std::is_sorted(v_str.begin(), v_str.end())
1661  && std::adjacent_find(v_str.begin(), v_str.end()) == v_str.end();
1662  };
1663  // Verify also that new_s contains all the symbols in m.
1664  auto verify_ss = [&m, &new_s]() -> bool {
1665  for (const auto &p : m) {
1666  for (const auto &s : p.second) {
1667  if (new_s.find(s) == new_s.end()) {
1668  return false;
1669  }
1670  }
1671  }
1672  return true;
1673  };
1674 #endif
1675  piranha_assert(verify_ins_map());
1676  piranha_assert(verify_ss());
1677  // Create the output.
1678  Derived retval;
1679  // Assign the new symbol set.
1680  retval.m_symbol_set = new_s;
1681  // Prepare a number of buckets equal to the current one.
1682  retval.m_container.rehash(this->m_container.bucket_count());
1683  // Do the symbol merge.
1684  const auto it_f = m_container.end();
1685  for (auto it = m_container.begin(); it != it_f; ++it) {
1686  retval.insert(term_type{it->m_cf, it->m_key.merge_symbols(m, this->m_symbol_set)});
1687  }
1688  return retval;
1689  }
1690  // Set of checks to be run on destruction in debug mode.
1691  bool destruction_checks() const
1692  {
1693  // Run destruction checks only if we are not in shutdown.
1694  if (shutdown()) {
1695  return true;
1696  }
1697  for (auto it = m_container.begin(); it != m_container.end(); ++it) {
1698  if (!it->is_compatible(m_symbol_set)) {
1699  std::cout << "Term not compatible.\n";
1700  return false;
1701  }
1702  if (it->is_zero(m_symbol_set)) {
1703  std::cout << "Term not ignorable.\n";
1704  return false;
1705  }
1706  }
1707  return true;
1708  }
1709  template <typename T>
1710  static T trim_cf_impl(const T &s, typename std::enable_if<is_series<T>::value>::type * = nullptr)
1711  {
1712  return s.trim();
1713  }
1714  template <typename T>
1715  static const T &trim_cf_impl(const T &s, typename std::enable_if<!is_series<T>::value>::type * = nullptr)
1716  {
1717  return s;
1718  }
1719  // Metaprogramming bits for partial derivative.
1720  template <typename Cf2>
1721  using cf_diff_type = decltype(math::partial(std::declval<const Cf2 &>(), std::string()));
1722  // NOTE: decltype on a member is the type of that member:
1723  // http://thbecker.net/articles/auto_and_decltype/section_06.html
1724  template <typename Key2>
1725  using key_diff_type
1726  = decltype(std::declval<const Key2 &>().partial(symbol_idx{}, std::declval<const symbol_fset &>()).first);
1727  // Shortcuts to get cf/key from series.
1728  template <typename Series>
1729  using cf_t = typename Series::term_type::cf_type;
1730  template <typename Series>
1731  using key_t = typename Series::term_type::key_type;
1732  // We have different implementations, depending on whether the computed derivative type is the same as the original
1733  // one (in which case we will use faster term insertions) or not (in which case we resort to series arithmetics).
1734  // Adopt the usual scheme of providing an unspecialised value that sfinaes out if the enabler condition is
1735  // malformed, and two specialisations that implement the logic above.
1736  template <typename Series, typename = void>
1737  struct partial_type_ {
1738  };
1739 // NOTE: the enabler conditions below are one the negation of the other, not sure if it is possible to avoid repetition
1740 // without macro as we need
1741 // them explicitly for sfinae.
1742 #define PIRANHA_SERIES_PARTIAL_ENABLER \
1743  (std::is_same<cf_diff_type<cf_t<Series>>, cf_t<Series>>::value \
1744  && std::is_same<decltype(std::declval<const cf_t<Series> &>() \
1745  * std::declval<const key_diff_type<key_t<Series>> &>()), \
1746  cf_t<Series>>::value)
1747  template <typename Series>
1748  struct partial_type_<Series, typename std::enable_if<PIRANHA_SERIES_PARTIAL_ENABLER>::type> {
1749  using type = Series;
1750  // Record the algorithm to be adopted for later use.
1751  static const int algo = 0;
1752  };
1753  // This is the type resulting from the second algorithm.
1754  template <typename Series>
1755  using partial_type_1
1756  = decltype(std::declval<const cf_diff_type<cf_t<Series>> &>() * std::declval<const Series &>()
1757  + std::declval<const cf_t<Series> &>() * std::declval<const key_diff_type<key_t<Series>> &>()
1758  * std::declval<const Series &>());
1759  // NOTE: in addition to check that we cannot use the optimised algorithm, we must also check that the resulting type
1760  // is constructible from int (for the accumulation of retval) and that it is addable in-place.
1761  template <typename Series>
1762  struct partial_type_<
1763  Series, typename std::enable_if<
1764  !PIRANHA_SERIES_PARTIAL_ENABLER && std::is_constructible<partial_type_1<Series>, int>::value
1765  && is_addable_in_place<decltype(std::declval<const partial_type_1<Series> &>()
1766  + std::declval<const partial_type_1<Series> &>())>::value>::type> {
1767  using type = partial_type_1<Series>;
1768  static const int algo = 1;
1769  };
1770 #undef PIRANHA_SERIES_PARTIAL_ENABLER
1771  // The final typedef.
1772  template <typename Series>
1773  using partial_type = typename std::enable_if<is_returnable<typename partial_type_<Series>::type>::value,
1774  typename partial_type_<Series>::type>::type;
1775  // The implementation of the two partial() algorithms.
1776  template <typename Series = Derived, typename std::enable_if<partial_type_<Series>::algo == 0, int>::type = 0>
1777  partial_type<Series> partial_impl(const std::string &name) const
1778  {
1779  static_assert(std::is_same<Derived, partial_type<Series>>::value, "Invalid type.");
1780  // This is the faster algorithm.
1781  Derived retval;
1782  retval.m_symbol_set = this->m_symbol_set;
1783  // Prepare a number of buckets equal to the current one.
1784  retval.m_container.rehash(this->m_container.bucket_count());
1785  const auto pos = ss_index_of(retval.m_symbol_set, name);
1786  const auto it_f = this->m_container.end();
1787  for (auto it = this->m_container.begin(); it != it_f; ++it) {
1788  // NOTE: here the term being inserted cannot be incompatible as it->m_key is coming from
1789  // a series with the same symbol set. The worst that can happen is something going awry in the derivative of
1790  // the coefficient. If the derivative becomes zero, the insertion routine will not insert anything.
1791  retval.insert(term_type{math::partial(it->m_cf, name), it->m_key});
1792  // NOTE: if the partial of the key returns an incompatible key, an error will be raised.
1793  auto p_key = it->m_key.partial(pos, retval.m_symbol_set);
1794  retval.insert(term_type{it->m_cf * p_key.first, std::move(p_key.second)});
1795  }
1796  return retval;
1797  }
1798  template <typename Series = Derived, typename std::enable_if<partial_type_<Series>::algo == 1, int>::type = 0>
1799  partial_type<Series> partial_impl(const std::string &name) const
1800  {
1801  const auto pos = ss_index_of(this->m_symbol_set, name);
1802  partial_type<Series> retval(0);
1803  const auto it_f = this->m_container.end();
1804  for (auto it = this->m_container.begin(); it != it_f; ++it) {
1805  // Construct the two pieces of the derivative relating to the key
1806  // in the original term.
1807  Derived tmp0;
1808  tmp0.m_symbol_set = this->m_symbol_set;
1809  tmp0.insert(term_type{1, it->m_key});
1810  auto p_key = it->m_key.partial(pos, this->m_symbol_set);
1811  Derived tmp1;
1812  tmp1.m_symbol_set = this->m_symbol_set;
1813  tmp1.insert(term_type{1, std::move(p_key.second)});
1814  // Assemble everything into the return value.
1815  retval += math::partial(it->m_cf, name) * tmp0 + it->m_cf * p_key.first * tmp1;
1816  }
1817  return retval;
1818  }
1819  // Custom derivatives boilerplate.
1820  // Partial derivative of a series type, as defined by math::partial(). Note that here we cannot use
1821  // partial_type as Derived series might override the partial() method with their own implementation.
1822  template <typename Series>
1823  using series_p_type = decltype(math::partial(std::declval<const Series &>(), std::declval<const std::string &>()));
1824  template <typename Series>
1825  using cp_map_type = std::unordered_map<std::string, std::function<series_p_type<Series>(const Derived &)>>;
1826  // NOTE: here the initialisation of the static variable inside the body of the function
1827  // is guaranteed to be thread-safe. It should not matter too much as we always protect the access to it.
1828  template <typename Series = Derived>
1829  static cp_map_type<Series> &get_cp_map()
1830  {
1831  static cp_map_type<Series> cp_map;
1832  return cp_map;
1833  }
1834  template <typename F, typename Series>
1835  using custom_partial_enabler =
1836  typename std::enable_if<std::is_constructible<std::function<series_p_type<Series>(const Derived &)>, F>::value,
1837  int>::type;
1838  // Serialization support.
1839  friend class boost::serialization::access;
1840  template <class Archive>
1841  void save(Archive &ar, unsigned) const
1842  {
1843  // Serialize the symbol set.
1844  boost_save(ar, get_symbol_set().size());
1845  for (const auto &sym : get_symbol_set()) {
1846  boost_save(ar, sym);
1847  }
1848  // Serialize the series.
1849  boost_save(ar, size());
1850  for (const auto &t : _container()) {
1851  boost_save(ar, t.m_cf);
1852  boost_save(ar, boost_s11n_key_wrapper<typename term_type::key_type>{t.m_key, get_symbol_set()});
1853  }
1854  }
1855  template <class Archive>
1856  void load(Archive &ar, unsigned)
1857  {
1858  using ss_size_t = decltype(get_symbol_set().size());
1859  using s_size_t = decltype(size());
1860  // Erase s.
1861  *this = series{};
1862  // Recover the symbol set.
1863  // First the size.
1864  ss_size_t ss_size;
1865  boost_load(ar, ss_size);
1866  // NOTE: not sure if it's worth it to make this thread_local, as when resizing up new memory
1867  // allocs for each string are necessary anyway. Still, with the small string optimisation
1868  // it may be worth it.
1869  std::vector<std::string> vs;
1870  vs.resize(safe_cast<std::vector<std::string>::size_type>(ss_size));
1871  // Load the symbol names in a vector of strings.
1872  for (auto &str : vs) {
1873  boost_load(ar, str);
1874  }
1875  // Construct the symbol set and set it to the series.
1876  symbol_fset ss(vs.begin(), vs.end());
1877  set_symbol_set(ss);
1878  // Now recover the series.
1879  // Size first.
1880  s_size_t s_size;
1881  boost_load(ar, s_size);
1882  // Preallocate buckets.
1883  _container().rehash(
1884  boost::numeric_cast<s_size_t>(std::ceil(static_cast<double>(s_size) / _container().max_load_factor())));
1885  // Insert all the terms.
1886  for (s_size_t i = 0u; i < s_size; ++i) {
1887  // NOTE: the rationale for creating a new term each time is that if we move it, we have
1888  // no guarantees on the moved-from state (in particular, we cannot be certain that a moved-from
1889  // term can be deserialized into).
1890  term_type t;
1891  boost_load(ar, t.m_cf);
1892  boost_s11n_key_wrapper<typename term_type::key_type> w{t.m_key, ss};
1893  boost_load(ar, w);
1894  insert(std::move(t));
1895  }
1896  }
1897  BOOST_SERIALIZATION_SPLIT_MEMBER()
1898  // Exponentiation machinery.
1899  // The type resulting from the exponentiation of the coefficient of a series U to the power of T.
1900  template <typename T, typename U>
1901  using pow_cf_type
1902  = decltype(math::pow(std::declval<const typename U::term_type::cf_type &>(), std::declval<const T &>()));
1903  // Type resulting from exponentiation via multiplication.
1904  template <typename U>
1905  using pow_m_type = decltype(std::declval<const U &>() * std::declval<const U &>());
1906  // Common checks on the exponent.
1907  template <typename T>
1908  using pow_expo_checks = std::integral_constant<bool, conjunction<has_is_zero<T>, has_safe_cast<integer, T>>::value>;
1909  // Hashing utils for series.
1910  struct series_hasher {
1911  template <typename T>
1912  std::size_t operator()(const T &s) const
1913  {
1914  return s.hash();
1915  }
1916  };
1917  struct series_equal_to {
1918  template <typename T>
1919  bool operator()(const T &a, const T &b) const
1920  {
1921  return a.is_identical(b);
1922  }
1923  };
1924  template <typename Series>
1925  using pow_map_type = std::unordered_map<Series, std::vector<pow_m_type<Series>>, series_hasher, series_equal_to>;
1926  // NOTE: here, as in the custom derivative machinery, we need to pass through a static function
1927  // to get the cache because Derived is an incomplete type and we cannot thus use a static data member
1928  // involving Derived in series. Also, we need the Series template argument to inhibit the instantiation
1929  // of the function for series types that do not support exponentiation.
1930  template <typename Series = Derived>
1931  static pow_map_type<Series> &get_pow_cache()
1932  {
1933  static pow_map_type<Series> s_pow_cache;
1934  return s_pow_cache;
1935  }
1936  // Empty for sfinae.
1937  template <typename T, typename U, typename = void>
1938  struct pow_ret_type_ {
1939  };
1940  // Case 0: the exponentiation of the coefficient does not change its type.
1941  template <typename T, typename U>
1942  struct pow_ret_type_<
1943  T, U,
1944  enable_if_t<conjunction<
1945  std::is_same<pow_cf_type<T, U>, typename U::term_type::cf_type>, pow_expo_checks<T>,
1946  // Check we can construct the return value from the type stored
1947  // in the cache.
1948  std::is_constructible<U, pow_m_type<U>>,
1949  // Check that when we multiply the type stored in the cache by
1950  // *this, we get again the type stored in the cache.
1951  std::is_same<pow_m_type<U>, decltype(std::declval<const pow_m_type<U> &>() * std::declval<const U &>())>,
1952  // Check we can use is_identical.
1953  std::is_same<is_identical_enabler<U>, int>>::value>> {
1954  using type = U;
1955  };
1956  // Case 1: the exponentiation of the coefficient does change its type.
1957  template <typename T, typename U>
1958  struct pow_ret_type_<
1959  T, U,
1960  enable_if_t<conjunction<
1961  negation<std::is_same<pow_cf_type<T, U>, typename U::term_type::cf_type>>, pow_expo_checks<T>,
1962  // Check we can construct the return value from the type stored
1963  // in the cache.
1964  std::is_constructible<series_rebind<U, pow_cf_type<T, U>>, pow_m_type<U>>,
1965  // Check that when we multiply the type stored in the cache by
1966  // *this, we get again the type stored in the cache.
1967  std::is_same<pow_m_type<U>, decltype(std::declval<const pow_m_type<U> &>() * std::declval<const U &>())>,
1968  // Check we can use is_identical.
1969  std::is_same<is_identical_enabler<U>, int>>::value>> {
1970  using type = series_rebind<U, pow_cf_type<T, U>>;
1971  };
1972  // Final typedef.
1973  template <typename T, typename U>
1974  using pow_ret_type = typename pow_ret_type_<T, U>::type;
1975 #endif
1976 public:
1978 
1983 
1994  using const_iterator = const_iterator_impl;
1996  series() = default;
1998 
2001  series(const series &) = default;
2003  series(series &&) = default;
2005 
2032  template <typename T, typename U = series, generic_ctor_enabler<T, U> = 0>
2033  explicit series(const T &x)
2034  {
2035  dispatch_generic_constructor(x);
2036  }
2039  {
2040  PIRANHA_TT_CHECK(std::is_base_of, series, Derived);
2041  PIRANHA_TT_CHECK(is_series, Derived);
2043  // Static checks on the iterator types.
2045  piranha_assert(destruction_checks());
2046  }
2048 
2055  series &operator=(const series &other)
2056  {
2057  if (likely(this != &other)) {
2058  series tmp(other);
2059  *this = std::move(tmp);
2060  }
2061  return *this;
2062  }
2064 
2069  series &operator=(series &&other) = default;
2071 
2084  template <typename T, typename U = series, generic_ctor_enabler<T, U> = 0>
2085  series &operator=(const T &x)
2086  {
2087  static_assert(!std::is_base_of<series, T>::value, "Generic assignment should not be enabled with "
2088  "a type deriving from the calling series.");
2089  return operator=(series(x));
2090  }
2092 
2095  size_type size() const
2096  {
2097  return m_container.size();
2098  }
2100 
2103  bool empty() const
2104  {
2105  return !size();
2106  }
2108 
2119  {
2120  return (empty() || (size() == 1u && m_container.begin()->m_key.is_unitary(m_symbol_set)));
2121  }
2123 
2160  template <bool Sign, typename T, insert_enabler<T> = 0>
2161  void insert(T &&term)
2162  {
2163  dispatch_insertion<Sign>(std::forward<T>(term));
2164  }
2166 
2176  template <typename T, insert_enabler<T> = 0>
2177  void insert(T &&term)
2178  {
2179  insert<true>(std::forward<T>(term));
2180  }
2182 
2187  Derived operator+() const
2188  {
2189  return Derived(*static_cast<Derived const *>(this));
2190  }
2192 
2199  Derived operator-() const
2200  {
2201  Derived retval(*static_cast<Derived const *>(this));
2202  retval.negate();
2203  return retval;
2204  }
2206 
2214  void negate()
2215  {
2216  try {
2217  const auto it_f = m_container.end();
2218  for (auto it = m_container.begin(); it != it_f;) {
2219  math::negate(it->m_cf);
2220  if (unlikely(it->is_zero(m_symbol_set))) {
2221  it = m_container.erase(it);
2222  } else {
2223  ++it;
2224  }
2225  }
2226  } catch (...) {
2227  m_container.clear();
2228  throw;
2229  }
2230  }
2235 
2244  sparsity_info_type table_sparsity() const
2245  {
2246  return m_container.evaluate_sparsity();
2247  }
2249 
2255  double table_load_factor() const
2256  {
2257  return m_container.load_factor();
2258  }
2260 
2264  {
2265  return m_container.bucket_count();
2266  }
2268 
2303  template <typename T, typename U = Derived>
2304  pow_ret_type<T, U> pow(const T &x) const
2305  {
2306  // NOTE: there are 3 types involved here:
2307  // - Derived,
2308  // - the return type (which is Derived or a rebound type from Derived),
2309  // - the type of Derived * Derived.
2310  using ret_type = pow_ret_type<T, U>;
2311  using r_term_type = typename ret_type::term_type;
2312  using r_cf_type = typename r_term_type::cf_type;
2313  using cf_type = typename term_type::cf_type;
2314  using key_type = typename term_type::key_type;
2315  using m_type = pow_m_type<Derived>;
2316  using m_term_type = typename m_type::term_type;
2317  using m_cf_type = typename m_term_type::cf_type;
2318  using m_key_type = typename m_term_type::key_type;
2319  // Handle the case of single coefficient series.
2320  if (is_single_coefficient()) {
2321  ret_type retval;
2322  if (empty()) {
2323  // An empty series is equal to zero.
2324  retval.insert(r_term_type(math::pow(cf_type(0), x), key_type(symbol_fset{})));
2325  } else {
2326  retval.insert(r_term_type(math::pow(m_container.begin()->m_cf, x), key_type(symbol_fset{})));
2327  }
2328  return retval;
2329  }
2330  // Handle the case of zero exponent.
2331  if (math::is_zero(x)) {
2332  ret_type retval;
2333  retval.insert(r_term_type(r_cf_type(1), key_type(symbol_fset{})));
2334  return retval;
2335  }
2336  // Exponentiation by repeated multiplications.
2337  integer n;
2338  try {
2339  n = safe_cast<integer>(x);
2340  } catch (const safe_cast_failure &) {
2341  piranha_throw(std::invalid_argument, "invalid argument for series exponentiation: non-integral value");
2342  }
2343  if (n.sgn() < 0) {
2344  piranha_throw(std::invalid_argument, "invalid argument for series exponentiation: negative integral value");
2345  }
2346  // Lock the cache for the rest of the method.
2347  std::lock_guard<std::mutex> lock(s_pow_mutex);
2348  auto &v = get_pow_cache()[*static_cast<Derived const *>(this)];
2349  using s_type = decltype(v.size());
2350  // Init the vector, if needed.
2351  if (!v.size()) {
2352  m_type tmp;
2353  tmp.insert(m_term_type(m_cf_type(1), m_key_type(symbol_fset{})));
2354  v.push_back(std::move(tmp));
2355  }
2356  // Fill in the missing powers.
2357  while (v.size() <= n) {
2358  // NOTE: for series it seems like it is better to run the dumb algorithm instead of, e.g.,
2359  // exponentiation by squaring - the growth in number of terms seems to be slower.
2360  v.push_back(v.back() * (*static_cast<Derived const *>(this)));
2361  }
2362  return ret_type(v[static_cast<s_type>(n)]);
2363  }
2365 
2370  template <typename T = Derived, is_identical_enabler<T> = 0>
2371  static void clear_pow_cache()
2372  {
2373  std::lock_guard<std::mutex> lock(s_pow_mutex);
2374  get_pow_cache().clear();
2375  }
2377 
2404  template <typename Series = Derived>
2405  partial_type<Series> partial(const std::string &name) const
2406  {
2407  return partial_impl(name);
2408  }
2410 
2431  template <typename F, typename Series = Derived, custom_partial_enabler<F, Series> = 0>
2432  static void register_custom_derivative(const std::string &name, F func)
2433  {
2434  using p_type = series_p_type<Derived>;
2435  using f_type = std::function<p_type(const Derived &)>;
2436  std::lock_guard<std::mutex> lock(s_cp_mutex);
2437  get_cp_map()[name] = f_type(func);
2438  }
2440 
2456  // NOTE: the additional template parameter is to disable the method in case the partial
2457  // type is malformed.
2458  template <typename Series = Derived, typename Partial = partial_type<Series>>
2459  static void unregister_custom_derivative(const std::string &name)
2460  {
2461  std::lock_guard<std::mutex> lock(s_cp_mutex);
2462  auto it = get_cp_map().find(name);
2463  if (it != get_cp_map().end()) {
2464  get_cp_map().erase(it);
2465  }
2466  }
2468 
2477  template <typename Series = Derived, typename Partial = partial_type<Series>>
2479  {
2480  std::lock_guard<std::mutex> lock(s_cp_mutex);
2481  get_cp_map().clear();
2482  }
2484 
2505  {
2506  typedef std::function<std::pair<typename term_type::cf_type, Derived>(const term_type &)> func_type;
2507  auto func
2508  = [this](const term_type &t) { return detail::pair_from_term<term_type, Derived>(this->m_symbol_set, t); };
2509  return boost::make_transform_iterator(m_container.begin(), func_type(std::move(func)));
2510  }
2512 
2524  {
2525  typedef std::function<std::pair<typename term_type::cf_type, Derived>(const term_type &)> func_type;
2526  auto func
2527  = [this](const term_type &t) { return detail::pair_from_term<term_type, Derived>(this->m_symbol_set, t); };
2528  return boost::make_transform_iterator(m_container.end(), func_type(std::move(func)));
2529  }
2531 
2547  Derived filter(std::function<bool(const std::pair<typename term_type::cf_type, Derived> &)> func) const
2548  {
2549  Derived retval;
2550  retval.m_symbol_set = m_symbol_set;
2551  const auto it_f = this->m_container.end();
2552  for (auto it = this->m_container.begin(); it != it_f; ++it) {
2553  if (func(detail::pair_from_term<term_type, Derived>(m_symbol_set, *it))) {
2554  retval.insert(*it);
2555  }
2556  }
2557  return retval;
2558  }
2560 
2584  // TODO require multipliability of cf * Derived and addability of the result to Derived in place.
2585  Derived transform(std::function<std::pair<typename term_type::cf_type, Derived>(
2586  const std::pair<typename term_type::cf_type, Derived> &)>
2587  func) const
2588  {
2589  Derived retval;
2590  std::pair<typename term_type::cf_type, Derived> tmp;
2591  const auto it_f = this->m_container.end();
2592  for (auto it = this->m_container.begin(); it != it_f; ++it) {
2593  tmp = func(detail::pair_from_term<term_type, Derived>(m_symbol_set, *it));
2594  // NOTE: here we could use multadd, but it seems like there's not
2595  // much benefit (plus, the types involved are different).
2596  retval += tmp.first * tmp.second;
2597  }
2598  return retval;
2599  }
2601 
2619  Derived trim() const
2620  {
2621  // Init the trimming mask.
2622  std::vector<char> trim_mask(safe_cast<std::vector<char>::size_type>(m_symbol_set.size()), char(1));
2623  // Determine the symbols to be trimmed.
2624  const auto it_f = this->m_container.end();
2625  for (auto it = this->m_container.begin(); it != it_f; ++it) {
2626  it->m_key.trim_identify(trim_mask, m_symbol_set);
2627  }
2628  // Build the retval.
2629  Derived retval;
2630  retval.m_symbol_set = ss_trim(m_symbol_set, trim_mask);
2631  for (auto it = this->m_container.begin(); it != it_f; ++it) {
2632  retval.insert(term_type{trim_cf_impl(it->m_cf), it->m_key.trim(trim_mask, m_symbol_set)});
2633  }
2634  return retval;
2635  }
2637 
2654  void print_tex(std::ostream &os) const
2655  {
2656  // If the series is empty, print zero and exit.
2657  if (empty()) {
2658  os << "0";
2659  return;
2660  }
2661  print_helper<true>(os, m_container.begin(), m_container.end(), m_symbol_set);
2662  }
2664 
2699  friend std::ostream &operator<<(std::ostream &os, const series &s)
2700  {
2701  // If the series is empty, print zero and exit.
2702  if (s.empty()) {
2703  os << "0";
2704  return os;
2705  }
2706  return print_helper<false>(os, s.m_container.begin(), s.m_container.end(), s.m_symbol_set);
2707  }
2709 
2724  // NOTE: hash and is_identical must always be considered together. E.g., two series can be identical even in case
2725  // of coefficient series which are not identical - but this does not matter, as is_identical implies strict
2726  // equivalence
2727  // of the keys and hash considers only the keys. If we wanted to consider also the coefficients for hashing, then we
2728  // need
2729  // (probably) to call recursively is_identical on coefficient series.
2730  std::size_t hash() const
2731  {
2732  std::size_t retval = 0u;
2733  const auto it_f = m_container.end();
2734  for (auto it = m_container.begin(); it != it_f; ++it) {
2735  retval += it->hash();
2736  }
2737  return retval;
2738  }
2740 
2753  template <typename T = Derived, is_identical_enabler<T> = 0>
2754  bool is_identical(const Derived &other) const
2755  {
2756  return m_symbol_set == other.m_symbol_set && other == *static_cast<Derived const *>(this);
2757  }
2759 
2763  {
2764  return m_symbol_set;
2765  }
2767 
2773  void set_symbol_set(const symbol_fset &args)
2774  {
2775  if (unlikely(!empty())) {
2776  piranha_throw(std::invalid_argument, "cannot set arguments on a non-empty series");
2777  }
2778  m_symbol_set = args;
2779  }
2784 
2789  {
2790  return m_container;
2791  }
2793 
2797  {
2798  return m_container;
2799  }
2801 protected:
2806 
2807 private:
2808  // Custom derivatives machinery.
2809  static std::mutex s_cp_mutex;
2810  // Pow cache machinery;
2811  static std::mutex s_pow_mutex;
2812 };
2813 
2814 template <typename Cf, typename Key, typename Derived>
2816 
2817 template <typename Cf, typename Key, typename Derived>
2819 
2820 inline namespace impl
2821 {
2822 
2823 // Implementation of the series merge helper.
2824 // This function will call f(s1,s2), after having possibly merged the symbol sets
2825 // of s1 and s2 if necessary (in which case, f will be called on copies of s1/s2).
2826 template <typename S1, typename S2, typename F>
2827 inline auto series_merge_f(S1 &&s1, S2 &&s2, const F &f) -> decltype(f(std::forward<S1>(s1), std::forward<S2>(s2)))
2828 {
2829  if (s1.get_symbol_set() == s2.get_symbol_set()) {
2830  // If the symbol sets are identical, just call f(s1,s2) directly.
2831  return f(std::forward<S1>(s1), std::forward<S2>(s2));
2832  }
2833  // Otherwise, we need to merge the symbol sets.
2834  const auto merge = ss_merge(s1.get_symbol_set(), s2.get_symbol_set());
2835  // Check that all possible return types are the same. This is necessary
2836  // as potentially we end up calling different overloads of f.
2837  static_assert(std::is_same<decltype(f(std::forward<S1>(s1), std::forward<S2>(s2))),
2838  decltype(f(s1.merge_arguments(std::get<0>(merge), std::get<1>(merge)),
2839  std::forward<S2>(s2)))>::value,
2840  "Inconsistent return type.");
2841  static_assert(std::is_same<decltype(f(std::forward<S1>(s1), std::forward<S2>(s2))),
2842  decltype(f(std::forward<S1>(s1),
2843  s2.merge_arguments(std::get<0>(merge), std::get<2>(merge))))>::value,
2844  "Inconsistent return type.");
2845  static_assert(std::is_same<decltype(f(std::forward<S1>(s1), std::forward<S2>(s2))),
2846  decltype(f(s1.merge_arguments(std::get<0>(merge), std::get<1>(merge)),
2847  s2.merge_arguments(std::get<0>(merge), std::get<2>(merge))))>::value,
2848  "Inconsistent return type.");
2849  // Check who needs a copy.
2850  const bool s1_needs_copy = (std::get<0>(merge) != s1.get_symbol_set()),
2851  s2_needs_copy = (std::get<0>(merge) != s2.get_symbol_set());
2852  const unsigned mask = unsigned(s1_needs_copy) + (unsigned(s2_needs_copy) << 1u);
2853  piranha_assert(mask != 0u);
2854  // Handle the 3 possible cases.
2855  switch (mask) {
2856  case 1u:
2857  return f(s1.merge_arguments(std::get<0>(merge), std::get<1>(merge)), std::forward<S2>(s2));
2858  case 2u:
2859  return f(std::forward<S1>(s1), s2.merge_arguments(std::get<0>(merge), std::get<2>(merge)));
2860  }
2861  // Put the last case outside the switch, so we avoid compiler warnings.
2862  return f(s1.merge_arguments(std::get<0>(merge), std::get<1>(merge)),
2863  s2.merge_arguments(std::get<0>(merge), std::get<2>(merge)));
2864 }
2865 }
2866 
2868 
2871 template <typename Series>
2872 struct print_coefficient_impl<Series, typename std::enable_if<is_series<Series>::value>::type> {
2874 
2883  void operator()(std::ostream &os, const Series &s) const
2884  {
2885  if (s.size() > 1u) {
2886  os << '(';
2887  }
2888  os << s;
2889  if (s.size() > 1u) {
2890  os << ')';
2891  }
2892  }
2893 };
2894 
2896 
2899 template <typename Series>
2900 struct print_tex_coefficient_impl<Series, typename std::enable_if<is_series<Series>::value>::type> {
2902 
2911  void operator()(std::ostream &os, const Series &s) const
2912  {
2913  if (s.size() > 1u) {
2914  os << "\\left(";
2915  }
2916  s.print_tex(os);
2917  if (s.size() > 1u) {
2918  os << "\\right)";
2919  }
2920  }
2921 };
2922 
2923 namespace math
2924 {
2925 
2927 
2930 template <typename T>
2931 struct negate_impl<T, typename std::enable_if<is_series<T>::value>::type> {
2933 
2938  template <typename U>
2939  void operator()(U &s) const
2940  {
2941  s.negate();
2942  }
2943 };
2944 
2946 
2950 template <typename Series>
2951 struct is_zero_impl<Series, typename std::enable_if<is_series<Series>::value>::type> {
2953 
2958  bool operator()(const Series &s) const
2959  {
2960  return s.empty();
2961  }
2962 };
2963 }
2964 
2965 inline namespace impl
2966 {
2967 
2968 // Enabler for the pow() specialisation for series.
2969 template <typename Series, typename T>
2970 using series_pow_member_t = decltype(std::declval<const Series &>().pow(std::declval<const T &>()));
2971 
2972 template <typename Series, typename T>
2973 using pow_series_enabler
2974  = enable_if_t<conjunction<is_series<Series>, is_detected<series_pow_member_t, Series, T>>::value>;
2975 }
2976 
2977 namespace math
2978 {
2979 
2981 
2985 template <typename Series, typename T>
2986 struct pow_impl<Series, T, pow_series_enabler<Series, T>> {
2987 private:
2988  using pow_type = series_pow_member_t<Series, T>;
2989 
2990 public:
2992 
3006  pow_type operator()(const Series &s, const T &x) const
3007  {
3008  return s.pow(x);
3009  }
3010 };
3011 }
3012 
3013 namespace detail
3014 {
3015 
3016 // Detect if series has a const invert() method.
3017 template <typename T>
3018 class series_has_invert : sfinae_types
3019 {
3020  template <typename U>
3021  static auto test(const U &t) -> decltype(t.invert(), void(), yes());
3022  static no test(...);
3023 
3024 public:
3025  static const bool value = std::is_same<decltype(test(std::declval<T>())), yes>::value;
3026 };
3027 
3028 // 1. Inversion via the custom method.
3029 template <typename T, typename std::enable_if<is_series<T>::value && series_has_invert<T>::value, int>::type = 0>
3030 inline auto series_invert_impl(const T &s) -> decltype(s.invert())
3031 {
3032  return s.invert();
3033 }
3034 
3035 struct series_cf_invert_functor {
3036  template <typename T>
3037  auto operator()(const T &x) const -> decltype(math::invert(x))
3038  {
3039  return math::invert(x);
3040  }
3041  static constexpr const char *name = "inverse";
3042 };
3043 
3044 // 2. Coefficient type supports math::invert(), with return type equal to coefficient type.
3045 // NOTE: here we are passing a series<> parameter: this will require a conversion from whatever series type (Derived) to
3046 // its series<> base,
3047 // and thus this overload will never conflict with the overload 1., even if the series that we pass in actually has the
3048 // invert method.
3049 // This is useful because we can now force the use of this "base" implementation by simply casting a series to its base
3050 // type.
3051 // This is used, e.g., in the sin/cos overrides for poisson_series, and it is similar to what it is done for the pow()
3052 // overrides.
3053 template <typename Cf, typename Key, typename Derived,
3054  typename std::enable_if<
3056  && std::is_same<
3057  typename Derived::term_type::cf_type,
3058  decltype(math::invert(std::declval<const typename Derived::term_type::cf_type &>()))>::value,
3059  int>::type
3060  = 0>
3061 inline Derived series_invert_impl(const series<Cf, Key, Derived> &s)
3062 {
3063  return apply_cf_functor<series_cf_invert_functor, Derived>(s);
3064 }
3065 
3066 // 3. coefficient type supports math::invert() with a result different from the original coefficient type and the series
3067 // can be rebound to this new type.
3068 template <typename Cf, typename Key, typename Derived,
3069  typename std::enable_if<
3071  && !std::is_same<
3072  typename Derived::term_type::cf_type,
3073  decltype(math::invert(std::declval<const typename Derived::term_type::cf_type &>()))>::value,
3074  int>::type
3075  = 0>
3076 inline series_rebind<Derived, decltype(math::invert(std::declval<const typename Derived::term_type::cf_type &>()))>
3077 series_invert_impl(const series<Cf, Key, Derived> &s)
3078 {
3079  using ret_type
3080  = series_rebind<Derived, decltype(math::invert(std::declval<const typename Derived::term_type::cf_type &>()))>;
3081  return apply_cf_functor<series_cf_invert_functor, ret_type>(s);
3082 }
3083 
3084 // Final enabler condition for the invert implementation.
3085 template <typename T>
3086 using series_invert_enabler =
3087  typename std::enable_if<true_tt<decltype(series_invert_impl(std::declval<const T &>()))>::value>::type;
3088 }
3089 
3090 namespace math
3091 {
3092 
3094 
3100 template <typename Series>
3101 struct invert_impl<Series, detail::series_invert_enabler<Series>> {
3103 
3113  template <typename T>
3114  auto operator()(const T &s) const -> decltype(detail::series_invert_impl(s))
3115  {
3116  return detail::series_invert_impl(s);
3117  }
3118 };
3119 }
3120 
3121 namespace detail
3122 {
3123 
3124 // Detect if series has a const sin() method.
3125 template <typename T>
3126 class series_has_sin : sfinae_types
3127 {
3128  template <typename U>
3129  static auto test(const U &t) -> decltype(t.sin());
3130  static no test(...);
3131 
3132 public:
3133  static const bool value = is_returnable<decltype(test(std::declval<T>()))>::value;
3134 };
3135 
3136 // Three cases for sin() implementation.
3137 // 1. call the member function, if available.
3138 template <typename T, typename std::enable_if<is_series<T>::value && series_has_sin<T>::value, int>::type = 0>
3139 inline auto series_sin_impl(const T &s) -> decltype(s.sin())
3140 {
3141  return s.sin();
3142 }
3143 
3144 struct series_cf_sin_functor {
3145  template <typename T>
3146  auto operator()(const T &x) const -> decltype(math::sin(x))
3147  {
3148  return math::sin(x);
3149  }
3150  static constexpr const char *name = "sine";
3151 };
3152 
3153 // 2. coefficient type supports math::sin() with a result equal to the original coefficient type.
3154 // NOTE: this overload and the one below do not conflict with the one above because it takes a series
3155 // as input argument: when used on a concrete series type, it will have to go through a to-base
3156 // conversion in order to be selected.
3157 template <
3158  typename Cf, typename Key, typename Derived,
3159  typename std::enable_if<
3161  && std::is_same<typename Derived::term_type::cf_type,
3162  decltype(math::sin(std::declval<const typename Derived::term_type::cf_type &>()))>::value,
3163  int>::type
3164  = 0>
3165 inline Derived series_sin_impl(const series<Cf, Key, Derived> &s)
3166 {
3167  return apply_cf_functor<series_cf_sin_functor, Derived>(s);
3168 }
3169 
3170 // 3. coefficient type supports math::sin() with a result different from the original coefficient type and the series
3171 // can be rebound to this new type.
3172 template <
3173  typename Cf, typename Key, typename Derived,
3174  typename std::enable_if<
3176  && !std::is_same<typename Derived::term_type::cf_type,
3177  decltype(math::sin(std::declval<const typename Derived::term_type::cf_type &>()))>::value,
3178  int>::type
3179  = 0>
3180 inline series_rebind<Derived, decltype(math::sin(std::declval<const typename Derived::term_type::cf_type &>()))>
3181 series_sin_impl(const series<Cf, Key, Derived> &s)
3182 {
3183  using ret_type
3184  = series_rebind<Derived, decltype(math::sin(std::declval<const typename Derived::term_type::cf_type &>()))>;
3185  return apply_cf_functor<series_cf_sin_functor, ret_type>(s);
3186 }
3187 
3188 // Final enabler condition for the sin implementation.
3189 template <typename T>
3190 using series_sin_enabler =
3191  typename std::enable_if<true_tt<decltype(series_sin_impl(std::declval<const T &>()))>::value>::type;
3192 
3193 // All of the above, but for cos().
3194 template <typename T>
3195 class series_has_cos : sfinae_types
3196 {
3197  template <typename U>
3198  static auto test(const U &t) -> decltype(t.cos());
3199  static no test(...);
3200 
3201 public:
3202  static const bool value = is_returnable<decltype(test(std::declval<T>()))>::value;
3203 };
3204 
3205 template <typename T, typename std::enable_if<is_series<T>::value && series_has_cos<T>::value, int>::type = 0>
3206 inline auto series_cos_impl(const T &s) -> decltype(s.cos())
3207 {
3208  return s.cos();
3209 }
3210 
3211 struct series_cf_cos_functor {
3212  template <typename T>
3213  auto operator()(const T &x) const -> decltype(math::cos(x))
3214  {
3215  return math::cos(x);
3216  }
3217  static constexpr const char *name = "cosine";
3218 };
3219 
3220 template <
3221  typename Cf, typename Key, typename Derived,
3222  typename std::enable_if<
3224  && std::is_same<typename Derived::term_type::cf_type,
3225  decltype(math::cos(std::declval<const typename Derived::term_type::cf_type &>()))>::value,
3226  int>::type
3227  = 0>
3228 inline Derived series_cos_impl(const series<Cf, Key, Derived> &s)
3229 {
3230  return apply_cf_functor<series_cf_cos_functor, Derived>(s);
3231 }
3232 
3233 template <
3234  typename Cf, typename Key, typename Derived,
3235  typename std::enable_if<
3237  && !std::is_same<typename Derived::term_type::cf_type,
3238  decltype(math::cos(std::declval<const typename Derived::term_type::cf_type &>()))>::value,
3239  int>::type
3240  = 0>
3241 inline series_rebind<Derived, decltype(math::cos(std::declval<const typename Derived::term_type::cf_type &>()))>
3242 series_cos_impl(const series<Cf, Key, Derived> &s)
3243 {
3244  using ret_type
3245  = series_rebind<Derived, decltype(math::cos(std::declval<const typename Derived::term_type::cf_type &>()))>;
3246  return apply_cf_functor<series_cf_cos_functor, ret_type>(s);
3247 }
3248 
3249 template <typename T>
3250 using series_cos_enabler =
3251  typename std::enable_if<true_tt<decltype(series_cos_impl(std::declval<const T &>()))>::value>::type;
3252 }
3253 
3254 namespace math
3255 {
3256 
3258 
3265 template <typename Series>
3266 struct sin_impl<Series, detail::series_sin_enabler<Series>> {
3268 
3279  auto operator()(const Series &s) const -> decltype(detail::series_sin_impl(s))
3280  {
3281  return detail::series_sin_impl(s);
3282  }
3283 };
3284 
3286 
3293 template <typename Series>
3294 struct cos_impl<Series, detail::series_cos_enabler<Series>> {
3296 
3307  auto operator()(const Series &s) const -> decltype(detail::series_cos_impl(s))
3308  {
3309  return detail::series_cos_impl(s);
3310  }
3311 };
3312 }
3313 
3314 namespace detail
3315 {
3316 
3317 // Enabler for the partial() specialisation for series.
3318 template <typename Series>
3319 using series_partial_enabler = typename std::enable_if<is_series<Series>::value
3320  && is_returnable<decltype(std::declval<const Series &>().partial(
3321  std::declval<const std::string &>()))>::value>::type;
3322 
3323 // Enabler for the integrate() specialisation: type needs to be a series which supports the integration method.
3324 template <typename Series>
3325 using series_integrate_enabler =
3326  typename std::enable_if<is_series<Series>::value
3327  && is_returnable<decltype(std::declval<const Series &>().integrate(
3328  std::declval<const std::string &>()))>::value>::type;
3329 }
3330 
3331 namespace math
3332 {
3333 
3335 
3339 template <typename Series>
3340 struct partial_impl<Series, detail::series_partial_enabler<Series>> {
3342 
3358  template <typename T>
3359  auto operator()(const T &s, const std::string &name) const -> decltype(s.partial(name))
3360  {
3361  using partial_type = decltype(s.partial(name));
3362  bool custom = false;
3363  std::function<partial_type(const T &)> func;
3364  // Try to locate a custom partial derivative and copy it into func, if found.
3365  {
3366  std::lock_guard<std::mutex> lock(T::s_cp_mutex);
3367  auto it = s.get_cp_map().find(name);
3368  if (it != s.get_cp_map().end()) {
3369  func = it->second;
3370  custom = true;
3371  }
3372  }
3373  if (custom) {
3374  return func(s);
3375  }
3376  return s.partial(name);
3377  }
3378 };
3379 
3381 
3385 template <typename Series>
3386 struct integrate_impl<Series, detail::series_integrate_enabler<Series>> {
3388 
3396  template <typename T>
3397  auto operator()(const T &s, const std::string &name) const -> decltype(s.integrate(name))
3398  {
3399  return s.integrate(name);
3400  }
3401 };
3402 
3403 inline namespace impl
3404 {
3405 
3406 // This is the candidate evaluation type: the product of the evaluation of the coefficient by
3407 // the evaluation of the key.
3408 template <typename Series, typename T>
3409 using series_eval_type = decltype(
3410  math::evaluate(std::declval<const typename Series::term_type::cf_type &>(), std::declval<const symbol_fmap<T> &>())
3411  * std::declval<const typename Series::term_type::key_type &>().evaluate(std::declval<const std::vector<T> &>(),
3412  std::declval<const symbol_fset &>()));
3413 
3414 template <typename Series, typename T>
3415 using math_series_evaluate_enabler
3416  = enable_if_t<conjunction<is_series<Series>, is_addable_in_place<series_eval_type<Series, T>>,
3417  std::is_constructible<series_eval_type<Series, T>, const int &>,
3418  is_returnable<series_eval_type<Series, T>>, std::is_destructible<T>,
3419  std::is_copy_constructible<T>>::value>;
3420 }
3421 
3423 
3432 template <typename Series, typename T>
3433 class evaluate_impl<Series, T, math_series_evaluate_enabler<Series, T>>
3434 {
3435  using eval_type = series_eval_type<Series, T>;
3436  // Wrapper to do multadd either via math::multiply_accumulate(), if supported, or just
3437  // plain math operators.
3438  template <typename E, enable_if_t<has_multiply_accumulate<E>::value, int> = 0>
3439  static void multadd(E &retval, const E &a, const E &b)
3440  {
3441  math::multiply_accumulate(retval, a, b);
3442  }
3443  template <typename E1, typename E2, typename E3>
3444  static void multadd(E1 &retval, const E2 &a, const E3 &b)
3445  {
3446  retval += a * b;
3447  }
3448 
3449 public:
3451 
3469  eval_type operator()(const Series &s, const symbol_fmap<T> &dict) const
3470  {
3471  // NOTE: possible improvement: if the evaluation type is less-than comparable,
3472  // build a vector of evaluated terms, sort it and accumulate (to minimise accuracy loss
3473  // with fp types and maybe improve performance - e.g., for integers).
3474 
3475  // Cache a reference to the symbol set.
3476  const auto &ss = s.get_symbol_set();
3477 
3478  // Init the vector that will be used for key evaluation. Make it possibly
3479  // thread local in order to avoid allocations each time we invoke this function.
3480  PIRANHA_MAYBE_TLS std::vector<T> evec;
3481  // Make sure the static cached vector is reset to empty.
3482  evec.resize(0);
3483 
3484  auto it_dict = dict.begin();
3485  const auto it_dict_f = dict.end();
3486  for (const auto &sym : ss) {
3487  // Try to locate the current sym in the
3488  // [it_dict, it_dict_f) range. Store the result in it_dict.
3489  it_dict = std::lower_bound(
3490  it_dict, it_dict_f, sym,
3491  [](const std::pair<std::string, T> &p, const std::string &str) { return p.first < str; });
3492  // NOTE: if it_dict != it_dict_f, we found a value in the dict range which is >= sym,
3493  // but we still need to check it is really the same.
3494  if (unlikely(it_dict == it_dict_f || it_dict->first != sym)) {
3495  // The it_ss value was not found: we cannot evaluate.
3496  piranha_throw(std::invalid_argument, "cannot evaluate series: the symbol '" + sym
3497  + "' is missing from the series evaluation dictionary'");
3498  }
3499  // Append the value mapped to the current ss symbol to the vector.
3500  evec.push_back(it_dict->second);
3501  // NOTE: we increase it_dict at the end of the iteration because, if we
3502  // get there, it means we identified a symbol of ss in dict. For the next
3503  // iteration of the for loop we want to start looking for the next ss symbol
3504  // right *after* the element in dict we just found.
3505  ++it_dict;
3506  }
3507  piranha_assert(evec.size() == ss.size());
3508 
3509  // Init the return value and accumulate it.
3510  eval_type retval(0);
3511  for (const auto &t : s._container()) {
3512  multadd(retval, math::evaluate(t.m_cf, dict), t.m_key.evaluate(evec, ss));
3513  }
3514  return retval;
3515  }
3516 };
3517 }
3518 
3519 inline namespace impl
3520 {
3521 
3522 // NOTE: we check first here if Series is a series, so that, if it is not, we do not instantiate other has_boost_save
3523 // checks (which might result in infinite recursion due to this enabler being re-instantiated).
3524 template <typename Archive, typename Series>
3525 using series_boost_save_enabler = enable_if_t<
3526  conjunction<is_series<Series>, has_boost_save<Archive, decltype(symbol_fset{}.size())>,
3527  has_boost_save<Archive, const std::string &>,
3528  has_boost_save<Archive, decltype(std::declval<const Series &>().size())>,
3529  has_boost_save<Archive, typename Series::term_type::cf_type>,
3530  has_boost_save<Archive, boost_s11n_key_wrapper<typename Series::term_type::key_type>>>::value>;
3531 
3532 // NOTE: the requirement that Series must not be const is in the is_series check.
3533 template <typename Archive, typename Series>
3534 using series_boost_load_enabler = enable_if_t<conjunction<
3535  is_series<Series>, has_boost_load<Archive, decltype(symbol_fset{}.size())>, has_boost_load<Archive, std::string>,
3536  has_boost_load<Archive, decltype(std::declval<const Series &>().size())>,
3537  has_boost_load<Archive, typename Series::term_type::cf_type>,
3538  has_boost_load<Archive, boost_s11n_key_wrapper<typename Series::term_type::key_type>>>::value>;
3539 }
3540 
3542 
3552 template <typename Archive, typename Series>
3553 struct boost_save_impl<Archive, Series, series_boost_save_enabler<Archive, Series>>
3554  : boost_save_via_boost_api<Archive, Series> {
3555 };
3556 
3558 
3576 template <typename Archive, typename Series>
3577 struct boost_load_impl<Archive, Series, series_boost_load_enabler<Archive, Series>>
3578  : boost_load_via_boost_api<Archive, Series> {
3579 };
3580 
3581 #if defined(PIRANHA_WITH_MSGPACK)
3582 
3583 inline namespace impl
3584 {
3585 
3586 // Same scheme as above for Boost.
3587 template <typename Stream, typename Series>
3588 using series_msgpack_pack_enabler
3589  = enable_if_t<conjunction<is_series<Series>, is_msgpack_stream<Stream>, has_msgpack_pack<Stream, std::string>,
3590  has_safe_cast<std::uint32_t, decltype(symbol_fset{}.size())>,
3591  has_safe_cast<std::uint32_t, typename Series::size_type>,
3592  has_msgpack_pack<Stream, typename Series::term_type::cf_type>,
3593  key_has_msgpack_pack<Stream, typename Series::term_type::key_type>>::value>;
3594 
3595 template <typename Series>
3596 using series_msgpack_convert_enabler
3597  = enable_if_t<conjunction<is_series<Series>, has_msgpack_convert<std::string>,
3598  has_msgpack_convert<typename Series::term_type::cf_type>,
3599  key_has_msgpack_convert<typename Series::term_type::key_type>>::value>;
3600 }
3601 
3603 
3612 template <typename Stream, typename Series>
3613 struct msgpack_pack_impl<Stream, Series, series_msgpack_pack_enabler<Stream, Series>> {
3615 
3630  void operator()(msgpack::packer<Stream> &packer, const Series &s, msgpack_format f) const
3631  {
3632  // A series is an array made of:
3633  // - the symbol set,
3634  // - the array of terms.
3635  packer.pack_array(2u);
3636  // Pack the symbol set.
3637  const auto &ss = s.get_symbol_set();
3638  packer.pack_array(safe_cast<std::uint32_t>(ss.size()));
3639  for (const auto &sym : ss) {
3640  msgpack_pack(packer, sym, f);
3641  }
3642  // Pack the terms.
3643  packer.pack_array(safe_cast<std::uint32_t>(s.size()));
3644  for (const auto &t : s._container()) {
3645  // Each term is an array made of two elements, coefficient and key.
3646  packer.pack_array(2u);
3647  msgpack_pack(packer, t.m_cf, f);
3648  t.m_key.msgpack_pack(packer, f, ss);
3649  }
3650  }
3651 };
3652 
3654 
3661 template <typename Series>
3662 struct msgpack_convert_impl<Series, series_msgpack_convert_enabler<Series>> {
3664 
3684  void operator()(Series &s, const msgpack::object &o, msgpack_format f) const
3685  {
3686  using term_type = typename Series::term_type;
3687  using cf_type = typename term_type::cf_type;
3688  using key_type = typename term_type::key_type;
3689  using s_size_t = decltype(s.size());
3690  // Erase s.
3691  s = Series{};
3692  // Convert the object.
3693  std::array<std::vector<msgpack::object>, 2> tmp_v;
3694  o.convert(tmp_v);
3695  // Create the symbol set, passing through a vec of str.
3696  std::vector<std::string> v_str;
3697  std::transform(tmp_v[0].begin(), tmp_v[0].end(), std::back_inserter(v_str),
3698  [f](const msgpack::object &obj) -> std::string {
3699  std::string tmp_str;
3700  msgpack_convert(tmp_str, obj, f);
3701  return tmp_str;
3702  });
3703  s.set_symbol_set(symbol_fset(v_str.begin(), v_str.end()));
3704  // Preallocate buckets.
3705  s._container().rehash(boost::numeric_cast<s_size_t>(
3706  std::ceil(static_cast<double>(tmp_v[1].size()) / s._container().max_load_factor())));
3707  // Insert all the terms.
3708  for (const auto &t : tmp_v[1]) {
3709  std::array<msgpack::object, 2> tmp_term;
3710  t.convert(tmp_term);
3711  cf_type tmp_cf;
3712  key_type tmp_key;
3713  msgpack_convert(tmp_cf, tmp_term[0], f);
3714  tmp_key.msgpack_convert(tmp_term[1], f, s.get_symbol_set());
3715  s.insert(term_type{std::move(tmp_cf), std::move(tmp_key)});
3716  }
3717  }
3718 };
3719 
3720 #endif
3721 
3722 inline namespace impl
3723 {
3724 
3725 template <typename T>
3726 using series_zero_is_absorbing_enabler = enable_if_t<is_series<uncvref_t<T>>::value>;
3727 }
3728 
3730 
3737 template <typename T>
3738 struct zero_is_absorbing<T, series_zero_is_absorbing_enabler<T>> {
3739 private:
3740  PIRANHA_TT_CHECK(is_multipliable, uncvref_t<T>);
3741  static const bool implementation_defined = zero_is_absorbing<typename uncvref_t<T>::term_type::cf_type>::value;
3742 
3743 public:
3745  static const bool value = implementation_defined;
3746 };
3747 
3748 template <typename T>
3750 }
3751 
3752 #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
auto invert(const T &x) -> decltype(invert_impl< T >()(x))
Compute the inverse.
Definition: invert.hpp:79
auto print_tex_coefficient(std::ostream &os, const T &cf) -> decltype(print_tex_coefficient_impl< T >()(os, cf))
Print series coefficient in TeX mode.
Cf m_cf
Coefficient member.
Definition: term.hpp:189
auto operator()(const T &s, const std::string &name) const -> decltype(s.integrate(name))
Call operator.
Definition: series.hpp:3397
Derived transform(std::function< std::pair< typename term_type::cf_type, Derived >(const std::pair< typename term_type::cf_type, Derived > &)> func) const
Term transformation.
Definition: series.hpp:2585
Detect msgpack stream.
Definition: s11n.hpp:645
void boost_load(Archive &ar, T &x)
Load from Boost archive.
Definition: s11n.hpp:469
Default functor for the implementation of piranha::math::negate().
Definition: math.hpp:253
Multiprecision integer class.
Definition: mp++.hpp:869
friend bool operator!=(const T &x, const U &... y)
Inequality operator involving piranha::series.
Definition: series.hpp:1225
Default implementation of piranha::boost_load().
Definition: s11n.hpp:391
friend T & operator*=(T &x, U &&... y)
In-place multiplication involving piranha::series.
Definition: series.hpp:1142
mp_integer< 1 > integer
Alias for piranha::mp_integer with 1 limb of static storage.
Definition: mp_integer.hpp:63
void msgpack_convert(T &x, const msgpack::object &o, msgpack_format f)
Convert msgpack object.
Definition: s11n.hpp:957
detail::math_partial_type< T > partial(const T &x, const std::string &str)
Partial derivative.
Definition: math.hpp:691
Derived trim() const
Trim.
Definition: series.hpp:2619
auto operator()(const Series &s) const -> decltype(detail::series_sin_impl(s))
Call operator.
Definition: series.hpp:3279
Default functor for the implementation of piranha::math::integrate().
Definition: math.hpp:702
friend T & operator-=(T &x, U &&... y)
In-place subtraction involving piranha::series.
Definition: series.hpp:1102
Type trait to detect the availability of a series multiplier.
Definition: series.hpp:275
static const bool value
Value of the type trait.
Definition: convert_to.hpp:140
Default functor for the implementation of piranha::math::is_zero().
Definition: math.hpp:69
Implementation of piranha::boost_load() via the Boost API.
Definition: s11n.hpp:363
boost::container::flat_map< std::string, T > symbol_fmap
Flat map of symbols.
series_rebind_implementation< T, Cf > series_rebind
Rebind series.
Definition: series.hpp:225
In-place addable type trait.
detail::math_cos_type< T > cos(const T &x)
Cosine.
Definition: math.hpp:530
Exceptions.
Default functor for the implementation of piranha::math::sin().
Definition: math.hpp:541
sparsity_info_type table_sparsity() const
Table sparsity.
Definition: series.hpp:2244
const_iterator end() const
Const end iterator.
Definition: hash_set.hpp:883
Default implementation of piranha::boost_save().
Definition: s11n.hpp:245
static void register_custom_derivative(const std::string &name, F func)
Register custom partial derivative.
Definition: series.hpp:2432
void negate(T &x)
In-place negation.
Definition: math.hpp:329
STL namespace.
Default functor for the implementation of piranha::math::cos().
Definition: math.hpp:449
Key m_key
Key member.
Definition: term.hpp:191
Default functor for the implementation of piranha::math::evaluate().
Definition: math.hpp:753
pow_type operator()(const Series &s, const T &x) const
Call operator.
Definition: series.hpp:3006
bool is_single_coefficient() const
Test for single-coefficient series.
Definition: series.hpp:2118
Default functor for the implementation of piranha::math::pow().
Definition: pow.hpp:52
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
static void unregister_all_custom_derivatives()
Unregister all custom partial derivatives.
Definition: series.hpp:2478
series & operator=(const T &x)
Generic assignment operator.
Definition: series.hpp:2085
static unsigned long get_max_term_output()
Get max term output.
Definition: settings.hpp:202
Derived operator-() const
Negation operator.
Definition: series.hpp:2199
partial_type< Series > partial(const std::string &name) const
Partial derivative.
Definition: series.hpp:2405
std::tuple< symbol_fset, symbol_idx_fmap< symbol_fset >, symbol_idx_fmap< symbol_fset > > ss_merge(const symbol_fset &s1, const symbol_fset &s2)
Merge two symbol_fset.
bool is_identical(const Derived &other) const
Check for identical series.
Definition: series.hpp:2754
Input iterator type trait.
pow_ret_type< T, U > pow(const T &x) const
Exponentiation.
Definition: series.hpp:2304
math_evaluate_t< T, U > evaluate(const T &x, const symbol_fmap< U > &dict)
Evaluation.
Definition: math.hpp:831
Term class.
Definition: term.hpp:66
Default functor for the implementation of piranha::msgpack_convert().
Definition: s11n.hpp:867
Derived operator+() const
Identity operator.
Definition: series.hpp:2187
const_iterator_impl const_iterator
Const iterator.
Definition: series.hpp:1994
#define piranha_throw(exception_type,...)
Exception-throwing macro.
Definition: exceptions.hpp:118
boost::container::flat_set< std::string > symbol_fset
Flat set of symbols.
friend binary_mul_type< T, U... > operator*(T &&x, U &&... y)
Binary multiplication involving piranha::series.
Definition: series.hpp:1124
Key key_type
Alias for the key type.
Definition: term.hpp:79
Multipliable type trait.
static void unregister_custom_derivative(const std::string &name)
Unregister custom partial derivative.
Definition: series.hpp:2459
~series()
Trivial destructor.
Definition: series.hpp:2038
Derived filter(std::function< bool(const std::pair< typename term_type::cf_type, Derived > &)> func) const
Term filtering.
Definition: series.hpp:2547
Default functor for the implementation of piranha::msgpack_pack().
Definition: s11n.hpp:686
typename container_type::size_type size_type
Size type.
Definition: series.hpp:1981
msgpack_format
Serialization format for msgpack.
Definition: s11n.hpp:673
std::size_t hash() const
Hash value.
Definition: series.hpp:2730
Detect if zero is a multiplicative absorber.
Default functor for the implementation of piranha::math::partial().
Definition: math.hpp:633
const_iterator begin() const
Const begin iterator.
Definition: hash_set.hpp:859
Detect if type can be returned from a function.
friend binary_div_type< T, U... > operator/(T &&x, U &&... y)
Binary division involving piranha::series.
Definition: series.hpp:1164
bool is_compatible(const symbol_fset &args) const noexcept
Compatibility test.
Definition: term.hpp:170
Cf cf_type
Alias for the coefficient type.
Definition: term.hpp:77
symbol_fset::size_type symbol_idx
Symbol index.
const container_type & _container() const
Get a const reference to the container of terms.
Definition: series.hpp:2796
symbol_idx ss_index_of(const symbol_fset &ref, const std::string &name)
Identify the index of a symbol in a symbol_fset.
symbol_fset ss_trim(const symbol_fset &s, const std::vector< char > &mask)
Trim a symbol_fset.
bool is_zero(const T &x)
Zero test.
Definition: math.hpp:133
friend binary_sub_type< T, U... > operator-(T &&x, U &&... y)
Binary subtraction involving piranha::series.
Definition: series.hpp:1084
const_iterator begin() const
Begin iterator.
Definition: series.hpp:2504
container_type & _container()
Get a mutable reference to the container of terms.
Definition: series.hpp:2788
Implementation of piranha::boost_save() via the Boost API.
Definition: s11n.hpp:217
Series class.
Definition: series_fwd.hpp:45
bool empty() const
Empty test.
Definition: series.hpp:2103
Root piranha namespace.
Definition: array_key.hpp:52
double table_load_factor() const
Table load factor.
Definition: series.hpp:2255
void msgpack_pack(msgpack::packer< Stream > &packer, const T &x, msgpack_format f)
Pack generic object in a msgpack stream.
Definition: s11n.hpp:856
Detect the presence of piranha::msgpack_pack().
Definition: s11n.hpp:607
friend T & operator/=(T &x, U &&... y)
In-place division involving piranha::series.
Definition: series.hpp:1182
Type traits.
const symbol_fset & get_symbol_set() const
Symbol set getter.
Definition: series.hpp:2762
void multiply_accumulate(T &x, const T &y, const T &z)
Multiply-accumulate.
Definition: math.hpp:438
static const bool value
Value of the type trait.
Definition: series.hpp:286
std::size_t size_type
Size type.
Definition: hash_set.hpp:384
const_iterator end() const
End iterator.
Definition: series.hpp:2523
series & operator=(const series &other)
Copy-assignment operator.
Definition: series.hpp:2055
friend T & operator+=(T &x, U &&... y)
In-place addition involving piranha::series.
Definition: series.hpp:1062
void negate()
Negate series in-place.
Definition: series.hpp:2214
auto operator()(const Series &s) const -> decltype(detail::series_cos_impl(s))
Call operator.
Definition: series.hpp:3307
void operator()(msgpack::packer< Stream > &packer, const Series &s, msgpack_format f) const
Call operator.
Definition: series.hpp:3630
eval_type operator()(const Series &s, const symbol_fmap< T > &dict) const
Call operator.
Definition: series.hpp:3469
void insert(T &&term)
Insert term.
Definition: series.hpp:2161
auto operator()(const T &s) const -> decltype(detail::series_invert_impl(s))
Call operator.
Definition: series.hpp:3114
static const bool value
Value of the type trait.
friend std::ostream & operator<<(std::ostream &os, const series &s)
Overloaded stream operator for piranha::series.
Definition: series.hpp:2699
auto operator()(const T &s, const std::string &name) const -> decltype(s.partial(name))
Call operator.
Definition: series.hpp:3359
Type trait to detect piranha::safe_cast().
Definition: safe_cast.hpp:237
auto print_coefficient(std::ostream &os, const T &cf) -> decltype(print_coefficient_impl< T >()(os, cf))
Print series coefficient.
void boost_save(Archive &ar, const T &x)
Save to Boost archive.
Definition: s11n.hpp:328
series(const T &x)
Generic constructor.
Definition: series.hpp:2033
Series operators.
Definition: series.hpp:539
Default functor for the implementation of piranha::math::invert().
Definition: invert.hpp:50
void set_symbol_set(const symbol_fset &args)
Symbol set setter.
Definition: series.hpp:2773
symbol_fset m_symbol_set
Symbol set.
Definition: series.hpp:2803
friend bool operator==(const T &x, const U &... y)
Equality operator involving piranha::series.
Definition: series.hpp:1207
size_type table_bucket_count() const
Table bucket count.
Definition: series.hpp:2263
Series recursion index.
Definition: series.hpp:237
container_type m_container
Terms container.
Definition: series.hpp:2805
void insert(T &&term)
Insert generic term with Sign = true.
Definition: series.hpp:2177
Default functor for piranha::print_tex_coefficient().
static const bool value
Value of the type trait.
Type trait for well-behaved container elements.
int sgn() const
Sign.
Definition: mp++.hpp:1611
#define PIRANHA_TT_CHECK(tt,...)
Macro for static type trait checks.
static const bool value
Value of the type trait.
Definition: series.hpp:149
Type trait to detect series types.
Definition: series_fwd.hpp:49
friend binary_add_type< T, U... > operator+(T &&x, U &&... y)
Binary addition involving piranha::series.
Definition: series.hpp:1044
Check if a series can be rebound.
Definition: series.hpp:197
Default functor for piranha::print_coefficient().
Exception to signal failure in piranha::safe_cast().
Definition: safe_cast.hpp:53
detail::math_sin_type< T > sin(const T &x)
Sine.
Definition: math.hpp:622
void print_tex(std::ostream &os) const
Print in TeX mode.
Definition: series.hpp:2654
bool is_zero(const symbol_fset &args) const noexcept
Zero test.
Definition: term.hpp:184
void operator()(Series &s, const msgpack::object &o, msgpack_format f) const
Call operator.
Definition: series.hpp:3684
safe_cast_type< To, From > safe_cast(const From &x)
Safe cast.
Definition: safe_cast.hpp:219
static const std::size_t value
Value of the recursion index.
Definition: series.hpp:243
static const bool value
Value of the type trait.
Definition: series.hpp:203