1 /* Copyright 2009-2016 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 #include "../src/base_series_multiplier.hpp"
30 
31 #define BOOST_TEST_MODULE base_series_multiplier_test
32 #include <boost/test/included/unit_test.hpp>
33 
34 #include <algorithm>
35 #include <array>
36 #include <boost/mpl/for_each.hpp>
37 #include <boost/mpl/vector.hpp>
38 #include <cstddef>
39 #include <iterator>
40 #include <limits>
41 #include <set>
42 #include <stdexcept>
43 #include <type_traits>
44 #include <unordered_set>
45 #include <utility>
46 #include <vector>
47 
48 #include "../src/init.hpp"
49 #include "../src/kronecker_monomial.hpp"
50 #include "../src/monomial.hpp"
51 #include "../src/mp_integer.hpp"
52 #include "../src/mp_rational.hpp"
53 #include "../src/polynomial.hpp"
54 #include "../src/settings.hpp"
55 #include "../src/symbol.hpp"
56 #include "../src/symbol_set.hpp"
57 #include "../src/tuning.hpp"
58 #include "../src/type_traits.hpp"
59 
60 using namespace piranha;
61 
62 template <typename Cf>
63 using p_type = polynomial<Cf, monomial<int>>;
64 
65 template <typename Series>
66 struct m_checker : public base_series_multiplier<Series> {
67     using base = base_series_multiplier<Series>;
68     using size_type = typename base::size_type;
m_checkerm_checker69     explicit m_checker(const Series &s1, const Series &s2) : base(s1, s2)
70     {
71         BOOST_CHECK(!std::is_constructible<base>::value);
72         BOOST_CHECK(!std::is_copy_constructible<base>::value);
73         BOOST_CHECK(!std::is_move_constructible<base>::value);
74         BOOST_CHECK(!std::is_copy_assignable<base>::value);
75         BOOST_CHECK(!std::is_move_assignable<base>::value);
76         term_pointers_checker(s1, s2);
77         null_absorber_checker(s1, s2);
78     }
79     template <typename T, enable_if_t<zero_is_absorbing<typename T::term_type::cf_type>::value, int> = 0>
null_absorber_checkerm_checker80     void null_absorber_checker(const T &s1_, const T &s2_) const
81     {
82         const T &s1 = s1_.size() < s2_.size() ? s2_ : s1_;
83         const T &s2 = s1_.size() < s2_.size() ? s1_ : s2_;
84         BOOST_CHECK(s1.size() == this->m_v1.size());
85         BOOST_CHECK(s2.size() == this->m_v2.size());
86     }
87     template <typename T, enable_if_t<!zero_is_absorbing<typename T::term_type::cf_type>::value, int> = 0>
null_absorber_checkerm_checker88     void null_absorber_checker(const T &s1_, const T &s2_) const
89     {
90         const T &s1 = s1_.size() < s2_.size() ? s2_ : s1_;
91         const T &s2 = s1_.size() < s2_.size() ? s1_ : s2_;
92         BOOST_CHECK((s1.size() && s1.size() == this->m_v1.size())
93                     || (!s1.size() && this->m_v1.size() == 1u && this->m_v1[0]->m_cf == 0));
94         BOOST_CHECK((s2.size() && s2.size() == this->m_v2.size())
95                     || (!s2.size() && this->m_v2.size() == 1u && this->m_v2[0]->m_cf == 0));
96     }
97     template <typename T, enable_if_t<std::is_same<typename T::term_type::cf_type, double>::value, int> = 0>
term_pointers_checkerm_checker98     void term_pointers_checker(const T &, const T &) const
99     {
100         // Don't do anything for double, as we use it only to check the null absorber.
101     }
102     template <typename T,
103               typename std::enable_if<std::is_same<typename T::term_type::cf_type, integer>::value, int>::type = 0>
term_pointers_checkerm_checker104     void term_pointers_checker(const T &s1_, const T &s2_) const
105     {
106         // Swap the operands if needed.
107         const T &s1 = s1_.size() < s2_.size() ? s2_ : s1_;
108         const T &s2 = s1_.size() < s2_.size() ? s1_ : s2_;
109         BOOST_CHECK(s1.size() == this->m_v1.size());
110         BOOST_CHECK(s2.size() == this->m_v2.size());
111         // Create hash sets with the term pointers from the vectors.
112         std::unordered_set<const typename T::term_type *> h1, h2;
113         std::copy(this->m_v1.begin(), this->m_v1.end(), std::inserter(h1, h1.begin()));
114         std::copy(this->m_v2.begin(), this->m_v2.end(), std::inserter(h2, h2.begin()));
115         for (const auto &t : s1._container()) {
116             BOOST_CHECK(h1.find(&t) != h1.end());
117         }
118         for (const auto &t : s2._container()) {
119             BOOST_CHECK(h2.find(&t) != h2.end());
120         }
121     }
122     template <typename T,
123               typename std::enable_if<std::is_same<typename T::term_type::cf_type, rational>::value, int>::type = 0>
term_pointers_checkerm_checker124     void term_pointers_checker(const T &s1_, const T &s2_) const
125     {
126         const T &s1 = s1_.size() < s2_.size() ? s2_ : s1_;
127         const T &s2 = s1_.size() < s2_.size() ? s1_ : s2_;
128         BOOST_CHECK(s1.size() == this->m_v1.size());
129         BOOST_CHECK(s2.size() == this->m_v2.size());
130         std::unordered_set<const typename T::term_type *> h1, h2;
131         std::transform(s1._container().begin(), s1._container().end(), std::inserter(h1, h1.begin()),
132                        [](const typename T::term_type &t) { return &t; });
133         std::transform(s2._container().begin(), s2._container().end(), std::inserter(h2, h2.begin()),
134                        [](const typename T::term_type &t) { return &t; });
135         for (size_type i = 0u; i != s1.size(); ++i) {
136             BOOST_CHECK(h1.find(this->m_v1[i]) == h1.end());
137             BOOST_CHECK(this->m_v1[i]->m_cf.den() == 1);
138             auto it = s1._container().find(*this->m_v1[i]);
139             BOOST_CHECK(it != s1._container().end());
140             BOOST_CHECK(this->m_v1[i]->m_cf.num() % it->m_cf.num() == 0);
141         }
142         for (size_type i = 0u; i != s2.size(); ++i) {
143             BOOST_CHECK(h2.find(this->m_v2[i]) == h2.end());
144             BOOST_CHECK(this->m_v2[i]->m_cf.den() == 1);
145             auto it = s2._container().find(*this->m_v2[i]);
146             BOOST_CHECK(it != s2._container().end());
147             BOOST_CHECK(this->m_v2[i]->m_cf.num() % it->m_cf.num() == 0);
148         }
149     }
150     // Perfect forwarding of protected members, to make them accessible.
151     template <typename... Args>
blocked_multiplicationm_checker152     void blocked_multiplication(Args &&... args) const
153     {
154         base::blocked_multiplication(std::forward<Args>(args)...);
155     }
156     template <std::size_t N, typename MultFunctor, typename... Args>
estimate_final_series_sizem_checker157     typename Series::size_type estimate_final_series_size(Args &&... args) const
158     {
159         return base::template estimate_final_series_size<N, MultFunctor>(std::forward<Args>(args)...);
160     }
161     template <typename... Args>
sanitise_seriesm_checker162     static void sanitise_series(Args &&... args)
163     {
164         return base::sanitise_series(std::forward<Args>(args)...);
165     }
166     template <typename... Args>
plain_multiplicationm_checker167     Series plain_multiplication(Args &&... args) const
168     {
169         return base::plain_multiplication(std::forward<Args>(args)...);
170     }
171     template <typename... Args>
finalise_seriesm_checker172     void finalise_series(Args &&... args) const
173     {
174         base::finalise_series(std::forward<Args>(args)...);
175     }
get_n_threadsm_checker176     unsigned get_n_threads() const
177     {
178         return this->m_n_threads;
179     }
180 };
181 
BOOST_AUTO_TEST_CASE(base_series_multiplier_constructor_test)182 BOOST_AUTO_TEST_CASE(base_series_multiplier_constructor_test)
183 {
184     init();
185     {
186         // Check with empty series.
187         using pt = p_type<rational>;
188         pt e1, e2;
189         BOOST_CHECK_NO_THROW((m_checker<pt>{e1, e2}));
190         m_checker<pt> mc{e1, e2};
191         BOOST_CHECK(mc.get_n_threads() != 0u);
192     }
193     {
194         using pt = p_type<rational>;
195         pt x{"x"}, y{"y"}, z{"z"};
196         auto s1 = (x / 2 + y / 5).pow(5), s2 = (x / 3 + y / 22).pow(6);
197         m_checker<pt> m0(s1, s2);
198         std::swap(s1, s2);
199         m_checker<pt> m1(s1, s2);
200         s1 = 0;
201         s2 = 0;
202         m_checker<pt> m2(s1, s2);
203         BOOST_CHECK_THROW(m_checker<pt>(x, z), std::invalid_argument);
204     }
205     {
206         using pt = p_type<integer>;
207         pt x{"x"}, y{"y"}, z{"z"};
208         auto s1 = (x + y * 2).pow(5), s2 = (-x + y).pow(6);
209         m_checker<pt> m0(s1, s2);
210         std::swap(s1, s2);
211         m_checker<pt> m1(s1, s2);
212         s1 = 0;
213         s2 = 0;
214         m_checker<pt> m2(s1, s2);
215         BOOST_CHECK_THROW(m_checker<pt>(x, z), std::invalid_argument);
216     }
217     {
218         // Do a test with floating-point and null series.
219         using pt = p_type<double>;
220         pt x{"x"}, null{x - x};
221         m_checker<pt> m1((x + 1).pow(5), null);
222         m_checker<pt> m2(null, (x - 3).pow(5));
223         m_checker<pt> m3(null, null);
224     }
225 }
226 
227 // The sorting function for the definition of the set in the mult functor below.
228 struct p_sorter {
operator ()p_sorter229     bool operator()(const std::pair<unsigned, unsigned> &p1, const std::pair<unsigned, unsigned> &p2) const
230     {
231         if (p1.first < p2.first) {
232             return true;
233         }
234         if (p1.first == p2.first) {
235             return p1.second < p2.second;
236         }
237         return false;
238     }
239 };
240 
241 struct m_functor_0 {
242     m_functor_0() = default;
243     template <typename Series>
m_functor_0m_functor_0244     explicit m_functor_0(const base_series_multiplier<Series> &, Series &)
245     {
246     }
247     template <typename T>
operator ()m_functor_0248     void operator()(const T &i, const T &j) const
249     {
250         m_set.emplace(unsigned(i), unsigned(j));
251     }
252     mutable std::set<std::pair<unsigned, unsigned>, p_sorter> m_set;
253 };
254 
255 // A limit functor that will always return the construction parameter.
256 struct l_functor_0 {
l_functor_0l_functor_0257     l_functor_0(unsigned n) : m_n(n)
258     {
259     }
260     template <typename T>
operator ()l_functor_0261     T operator()(const T &) const
262     {
263         return T(m_n);
264     }
265     const unsigned m_n;
266 };
267 
BOOST_AUTO_TEST_CASE(base_series_multiplier_blocked_multiplication_test)268 BOOST_AUTO_TEST_CASE(base_series_multiplier_blocked_multiplication_test)
269 {
270     using pt = p_type<rational>;
271     pt x{"x"}, y{"y"};
272     auto s1 = (x + y).pow(100);
273     // Take out one term in order to make it exactly 100 terms.
274     s1 -= 1345860629046814650_z * x.pow(16) * y.pow(84);
275     m_checker<pt> m0(s1, s1);
276     tuning::set_multiplication_block_size(16u);
277     m_functor_0 mf0;
278     m0.blocked_multiplication(mf0, 0u, 100u);
279     BOOST_CHECK(mf0.m_set.size() == 100u * 100u);
280     for (unsigned i = 0u; i < 100u; ++i) {
281         for (unsigned j = 0u; j < 100u; ++j) {
282             BOOST_CHECK(mf0.m_set.count(std::make_pair(i, j)) == 1u);
283         }
284     }
285     // Try with commensurable block size.
286     tuning::set_multiplication_block_size(25u);
287     mf0.m_set.clear();
288     m0.blocked_multiplication(mf0, 0u, 100u);
289     BOOST_CHECK(mf0.m_set.size() == 100u * 100u);
290     for (unsigned i = 0u; i < 100u; ++i) {
291         for (unsigned j = 0u; j < 100u; ++j) {
292             BOOST_CHECK(mf0.m_set.count(std::make_pair(i, j)) == 1u);
293         }
294     }
295     // Block size same as series size.
296     tuning::set_multiplication_block_size(100u);
297     mf0.m_set.clear();
298     m0.blocked_multiplication(mf0, 0u, 100u);
299     BOOST_CHECK(mf0.m_set.size() == 100u * 100u);
300     for (unsigned i = 0u; i < 100u; ++i) {
301         for (unsigned j = 0u; j < 100u; ++j) {
302             BOOST_CHECK(mf0.m_set.count(std::make_pair(i, j)) == 1u);
303         }
304     }
305     // Larger size than series size.
306     tuning::set_multiplication_block_size(200u);
307     mf0.m_set.clear();
308     m0.blocked_multiplication(mf0, 0u, 100u);
309     BOOST_CHECK(mf0.m_set.size() == 100u * 100u);
310     for (unsigned i = 0u; i < 100u; ++i) {
311         for (unsigned j = 0u; j < 100u; ++j) {
312             BOOST_CHECK(mf0.m_set.count(std::make_pair(i, j)) == 1u);
313         }
314     }
315     // Only parts of the series.
316     tuning::set_multiplication_block_size(23u);
317     mf0.m_set.clear();
318     m0.blocked_multiplication(mf0, 20u, 87u);
319     BOOST_CHECK(mf0.m_set.size() == (87u - 20u) * 100u);
320     for (unsigned i = 20u; i < 87u; ++i) {
321         for (unsigned j = 0u; j < 100u; ++j) {
322             BOOST_CHECK(mf0.m_set.count(std::make_pair(i, j)) == 1u);
323         }
324     }
325     // Now with limits.
326     tuning::set_multiplication_block_size(16u);
327     mf0.m_set.clear();
328     m0.blocked_multiplication(mf0, 0u, 100u, l_functor_0{1u});
329     BOOST_CHECK(mf0.m_set.size() == 100u);
330     for (unsigned i = 0u; i < 100u; ++i) {
331         for (unsigned j = 0u; j < 1u; ++j) {
332             BOOST_CHECK(mf0.m_set.count(std::make_pair(i, j)) == 1u);
333         }
334     }
335     // No mults done.
336     tuning::set_multiplication_block_size(16u);
337     mf0.m_set.clear();
338     m0.blocked_multiplication(mf0, 0u, 100u, l_functor_0{0u});
339     BOOST_CHECK(mf0.m_set.size() == 0u);
340     // Try with commensurable block size.
341     tuning::set_multiplication_block_size(25u);
342     mf0.m_set.clear();
343     m0.blocked_multiplication(mf0, 0u, 100u, l_functor_0{2u});
344     BOOST_CHECK(mf0.m_set.size() == 100u * 2u);
345     for (unsigned i = 0u; i < 100u; ++i) {
346         for (unsigned j = 0u; j < 2u; ++j) {
347             BOOST_CHECK(mf0.m_set.count(std::make_pair(i, j)) == 1u);
348         }
349     }
350     // Block size same as series size.
351     tuning::set_multiplication_block_size(100u);
352     mf0.m_set.clear();
353     m0.blocked_multiplication(mf0, 0u, 100u, l_functor_0{2u});
354     BOOST_CHECK(mf0.m_set.size() == 100u * 2u);
355     for (unsigned i = 0u; i < 100u; ++i) {
356         for (unsigned j = 0u; j < 2u; ++j) {
357             BOOST_CHECK(mf0.m_set.count(std::make_pair(i, j)) == 1u);
358         }
359     }
360     // Larger size than series size.
361     tuning::set_multiplication_block_size(200u);
362     mf0.m_set.clear();
363     m0.blocked_multiplication(mf0, 0u, 100u, l_functor_0{2u});
364     BOOST_CHECK(mf0.m_set.size() == 100u * 2u);
365     for (unsigned i = 0u; i < 100u; ++i) {
366         for (unsigned j = 0u; j < 2u; ++j) {
367             BOOST_CHECK(mf0.m_set.count(std::make_pair(i, j)) == 1u);
368         }
369     }
370     // Only parts of the series.
371     tuning::set_multiplication_block_size(23u);
372     mf0.m_set.clear();
373     m0.blocked_multiplication(mf0, 20u, 87u, l_functor_0{2u});
374     BOOST_CHECK(mf0.m_set.size() == (87u - 20u) * 2u);
375     for (unsigned i = 20u; i < 87u; ++i) {
376         for (unsigned j = 1u; j < 2u; ++j) {
377             BOOST_CHECK(mf0.m_set.count(std::make_pair(i, j)) == 1u);
378         }
379     }
380     // Test error throwing.
381     BOOST_CHECK_THROW(m0.blocked_multiplication(mf0, 3u, 2u), std::invalid_argument);
382     BOOST_CHECK_THROW(m0.blocked_multiplication(mf0, 101u, 102u), std::invalid_argument);
383     BOOST_CHECK_THROW(m0.blocked_multiplication(mf0, 1u, 102u), std::invalid_argument);
384     // Try also with empty series, just to make sure.
385     pt e1, e2;
386     m_checker<pt> m1(e1, e2);
387     m_functor_0 mf1;
388     BOOST_CHECK_NO_THROW(m1.blocked_multiplication(mf1, 0u, 0u));
389     // Final reset of the mult block size.
390     tuning::reset_multiplication_block_size();
391 }
392 
BOOST_AUTO_TEST_CASE(base_series_multiplier_estimate_final_series_size_test)393 BOOST_AUTO_TEST_CASE(base_series_multiplier_estimate_final_series_size_test)
394 {
395     settings::set_min_work_per_thread(1u);
396     for (auto nt = 1u; nt < 4u; ++nt) {
397         using pt = p_type<integer>;
398         settings::set_n_threads(nt);
399         // Start with empty series.
400         pt e1, e2;
401         {
402             m_checker<pt> m0(e1, e2);
403             BOOST_CHECK_EQUAL((m0.estimate_final_series_size<1u, m_functor_0>()), 1u);
404         }
405         {
406             // Check with series with only one term.
407             e1 = 1;
408             e2 = 2;
409             m_checker<pt> m0(e1, e2);
410             BOOST_CHECK_EQUAL((m0.estimate_final_series_size<1u, m_functor_0>()), 1u);
411             BOOST_CHECK_EQUAL((m0.estimate_final_series_size<2u, m_functor_0>()), 2u);
412         }
413         {
414             // 1 by n terms.
415             e1 = 1 + pt{"x"} - pt{"x"};
416             e2 = 2;
417             e2 += pt{"x"};
418             m_checker<pt> m0(e1, e2);
419             BOOST_CHECK_EQUAL((m0.estimate_final_series_size<1u, m_functor_0>()), 2u);
420             BOOST_CHECK_EQUAL((m0.estimate_final_series_size<2u, m_functor_0>()), 4u);
421         }
422         {
423             // Check with total truncation.
424             e1 += pt{"x"};
425             m_checker<pt> m0(e1, e2);
426             BOOST_CHECK_EQUAL((m0.estimate_final_series_size<1u, m_functor_0>(l_functor_0{0u})), 1u);
427         }
428         // Just a couple of simple tests using polynomials, we can't really know what to expect as the method
429         // works in a statistical fashion.
430         {
431             pt x{"x"}, y{"y"};
432             auto a = (x + 2 * y + 4), b = (x * x - 2 * y * x - 3 - 4 * y);
433             m_checker<pt> m0(a, b);
434             // Here the multiplier functor does nothing, the estimation will exit immediately yielding 1.
435             BOOST_CHECK_EQUAL((m0.estimate_final_series_size<1u, m_functor_0>()), 1u);
436         }
437         // A reduced fateman1 benchmark, just to test a bit more.
438         {
439             pt x("x"), y("y"), z("z"), t("t");
440             auto f = x + y + z + t + 1;
441             auto tmp2(f);
442             for (auto i = 1; i < 10; ++i) {
443                 f *= tmp2;
444             }
445             auto b = f + 1;
446             auto retval = f * b;
447             std::cout << "Bucket count vs actual size: " << retval.table_bucket_count() << ',' << retval.size() << '\n';
448         }
449     }
450     settings::reset_min_work_per_thread();
451     settings::reset_n_threads();
452 }
453 
BOOST_AUTO_TEST_CASE(base_series_multiplier_sanitise_series_test)454 BOOST_AUTO_TEST_CASE(base_series_multiplier_sanitise_series_test)
455 {
456     using pt = p_type<integer>;
457     using mt = m_checker<pt>;
458     using term_type = typename pt::term_type;
459     std::array<unsigned, 4u> nt = {{1u, 2u, 3u, 4u}};
460     // Make sure the thread pool has 4 slots for testing
461     // below with various numbers of threads.
462     settings::set_n_threads(4u);
463     for (const auto &n : nt) {
464         // First test with an empty series.
465         pt e;
466         term_type tmp;
467         BOOST_CHECK_THROW(mt::sanitise_series(e, 0u), std::invalid_argument);
468         BOOST_CHECK_NO_THROW(mt::sanitise_series(e, n));
469         // Insert a term without updating the count.
470         tmp = term_type{1_z, term_type::key_type{}};
471         e._container().rehash(1u);
472         e._container()._unique_insert(tmp, 0u);
473         mt::sanitise_series(e, n);
474         BOOST_CHECK_EQUAL(e.size(), 1u);
475         // Try with a term with zero coefficient.
476         e._container().clear();
477         e._container().rehash(1u);
478         tmp = term_type{0_z, term_type::key_type{}};
479         e._container()._unique_insert(tmp, 0u);
480         mt::sanitise_series(e, n);
481         BOOST_CHECK_EQUAL(e.size(), 0u);
482         // Try with an incompatible term.
483         e._container().clear();
484         e._container().rehash(1u);
485         // NOTE: this is also ignorable, but the compatibility check is done first.
486         tmp = term_type{0_z, term_type::key_type{1}};
487         e._container()._unique_insert(tmp, 0u);
488         BOOST_CHECK_THROW(mt::sanitise_series(e, n), std::invalid_argument);
489         e._container().clear();
490         // Wrong size.
491         e._container().rehash(1u);
492         e._container()._update_size(3u);
493         tmp = term_type{2_z, term_type::key_type{}};
494         e._container()._unique_insert(tmp, 0u);
495         mt::sanitise_series(e, n);
496         BOOST_CHECK_EQUAL(e.size(), 1u);
497         // A test with multiple buckets.
498         e = pt{"x"} - pt{"x"}; // Just make sure we set the symbol set correctly.
499         e._container().clear();
500         e._container().rehash(16u);
501         e._container()._update_size(3u);
502         for (unsigned i = 0u; i < 10u; ++i) {
503             tmp = term_type{i, term_type::key_type{int(i)}};
504             e._container()._unique_insert(tmp, e._container()._bucket(tmp));
505         }
506         mt::sanitise_series(e, n);
507         BOOST_CHECK_EQUAL(e.size(), 9u);
508         // Also with incompatible term.
509         e._container().clear();
510         e._container().rehash(16u);
511         e._container()._update_size(3u);
512         for (unsigned i = 0u; i < 10u; ++i) {
513             tmp = term_type{i, term_type::key_type{int(i), int(i)}};
514             e._container()._unique_insert(tmp, e._container()._bucket(tmp));
515         }
516         BOOST_CHECK_THROW(mt::sanitise_series(e, n), std::invalid_argument);
517         e._container().clear();
518     }
519     // Reset to the default setup.
520     settings::reset_n_threads();
521 }
522 
523 struct multiplication_tester {
524     template <typename T>
operator ()multiplication_tester525     void operator()(const T &)
526     {
527         // NOTE: this test is going to be exact in case of coefficients cancellations with double
528         // precision coefficients only if the platform has ieee 754 format (integer exactly representable
529         // as doubles up to 2 ** 53).
530         if (std::is_same<typename T::term_type::cf_type, double>::value
531             && (!std::numeric_limits<double>::is_iec559 || std::numeric_limits<double>::digits < 53)) {
532             return;
533         }
534         T x("x"), y("y"), z("z"), t("t"), u("u");
535         // Dense case, default setup.
536         auto f = 1 + x + y + z + t;
537         auto tmp(f);
538         for (int i = 1; i < 10; ++i) {
539             f *= tmp;
540         }
541         auto g = f + 1;
542         auto retval = f * g;
543         BOOST_CHECK_EQUAL(retval.size(), 10626u);
544         // Test swapping.
545         BOOST_CHECK(x * (1 + x) == (1 + x) * x);
546         BOOST_CHECK(T(1) * retval == retval);
547         // Dense case, force number of threads.
548         for (auto i = 1u; i <= 4u; ++i) {
549             settings::set_n_threads(i);
550             auto tmp2 = f * g;
551             BOOST_CHECK_EQUAL(tmp2.size(), 10626u);
552             BOOST_CHECK(tmp2 == retval);
553         }
554         // Dense case, same input series.
555         settings::set_n_threads(4u);
556         {
557             auto tmp2 = f * f;
558             BOOST_CHECK_EQUAL(tmp2.size(), 10626u);
559         }
560         settings::reset_n_threads();
561         // Dense case with cancellations, default setup.
562         auto h = 1 - x + y + z + t;
563         tmp = h;
564         for (int i = 1; i < 10; ++i) {
565             h *= tmp;
566         }
567         retval = f * h;
568         BOOST_CHECK_EQUAL(retval.size(), 5786u);
569         // Dense case with cancellations, force number of threads.
570         for (auto i = 1u; i <= 4u; ++i) {
571             settings::set_n_threads(i);
572             auto tmp2 = f * h;
573             BOOST_CHECK_EQUAL(tmp2.size(), 5786u);
574             BOOST_CHECK(retval == tmp2);
575         }
576         settings::reset_n_threads();
577         // Sparse case, default.
578         f = (x + y + z * z * 2 + t * t * t * 3 + u * u * u * u * u * 5 + 1);
579         auto tmp_f(f);
580         g = (u + t + z * z * 2 + y * y * y * 3 + x * x * x * x * x * 5 + 1);
581         auto tmp_g(g);
582         h = (-u + t + z * z * 2 + y * y * y * 3 + x * x * x * x * x * 5 + 1);
583         auto tmp_h(h);
584         for (int i = 1; i < 8; ++i) {
585             f *= tmp_f;
586             g *= tmp_g;
587             h *= tmp_h;
588         }
589         retval = f * g;
590         BOOST_CHECK_EQUAL(retval.size(), 591235u);
591         // Sparse case, force n threads.
592         for (auto i = 1u; i <= 4u; ++i) {
593             settings::set_n_threads(i);
594             auto tmp2 = f * g;
595             BOOST_CHECK_EQUAL(tmp2.size(), 591235u);
596             BOOST_CHECK(retval == tmp2);
597         }
598         settings::reset_n_threads();
599         // Sparse case with cancellations, default.
600         retval = f * h;
601         BOOST_CHECK_EQUAL(retval.size(), 591184u);
602         // Sparse case with cancellations, force number of threads.
603         for (auto i = 1u; i <= 4u; ++i) {
604             settings::set_n_threads(i);
605             auto tmp2 = f * h;
606             BOOST_CHECK_EQUAL(tmp2.size(), 591184u);
607             BOOST_CHECK(tmp2 == retval);
608         }
609         settings::reset_n_threads();
610     }
611 };
612 
BOOST_AUTO_TEST_CASE(base_series_multiplier_plain_multiplication_test)613 BOOST_AUTO_TEST_CASE(base_series_multiplier_plain_multiplication_test)
614 {
615     {
616         // Simple test with empty series.
617         using pt = p_type<integer>;
618         using mt = m_checker<pt>;
619         pt e1, e2;
620         mt m0{e1, e2};
621         BOOST_CHECK_EQUAL(m0.plain_multiplication(), 0);
622         BOOST_CHECK(m0.get_n_threads() != 0u);
623         // Testing ported over from the previous series_multiplier tests. Just use polynomial directly.
624         using pt1 = p_type<double>;
625         using pt2 = p_type<integer>;
626         pt1 p1{"x"}, p2{"x"};
627         // Check that the merged symbol set is returned when one of the series is empty.
628         BOOST_CHECK(e1 * p1 == 0);
629         BOOST_CHECK((e1 * p1).get_symbol_set() == symbol_set{symbol{"x"}});
630         BOOST_CHECK((p1 * e1).get_symbol_set() == symbol_set{symbol{"x"}});
631         p1._container().begin()->m_cf *= 2;
632         p2._container().begin()->m_cf *= 3;
633         auto retval = p1 * p2;
634         BOOST_CHECK(retval.size() == 1u);
635         BOOST_CHECK(retval._container().begin()->m_key.size() == 1u);
636         BOOST_CHECK(retval._container().begin()->m_key[0] == 2);
637         BOOST_CHECK(retval._container().begin()->m_cf == (double(3) * double(1)) * (double(2) * double(1)));
638         pt2 p3{"x"};
639         p3._container().begin()->m_cf *= 4;
640         pt2 p4{"x"};
641         p4._container().begin()->m_cf *= 2;
642         retval = p4 * p3;
643         BOOST_CHECK(retval.size() == 1u);
644         BOOST_CHECK(retval._container().begin()->m_key.size() == 1u);
645         BOOST_CHECK(retval._container().begin()->m_key[0] == 2);
646         BOOST_CHECK(retval._container().begin()->m_cf == double((double(2) * double(1)) * (integer(1) * 4)));
647         using p_types = boost::mpl::vector<pt1, pt2, p_type<rational>>;
648         boost::mpl::for_each<p_types>(multiplication_tester());
649     }
650     {
651         // Some testing with double for the zero absorber modifications.
652         using pt = p_type<double>;
653         pt x{"x"}, y{"y"};
654         BOOST_CHECK_EQUAL((x + y + 1).pow(5) * pt(0), 0);
655         BOOST_CHECK_EQUAL(pt(0) * (x + y + 1).pow(5), 0);
656         BOOST_CHECK_EQUAL(pt(0) * pt(0), 0);
657         BOOST_CHECK_EQUAL((x - x + y - y) * (x - x + y - y), 0);
658     }
659 }
660 
BOOST_AUTO_TEST_CASE(base_series_multiplier_finalise_test)661 BOOST_AUTO_TEST_CASE(base_series_multiplier_finalise_test)
662 {
663     {
664         // Test proper handling of rational coefficients.
665         using pt = p_type<rational>;
666         pt x{"x"}, y{"y"};
667         BOOST_CHECK_EQUAL(x * 4 / 3_q * y * 5 / 2_q, 10 / 3_q * x * y);
668         BOOST_CHECK_EQUAL((x * 4 / 3_q + y * 5 / 2_q) * (x.pow(2) * 4 / 13_q - y * 5 / 17_q),
669                           16 * x.pow(3) / 39 + 10 / 13_q * y * x * x - 20 * x * y / 51 - 25 * y * y / 34);
670         // No finalisation happening with integral coefficients.
671         using pt2 = p_type<integer>;
672         pt2 x2{"x"}, y2{"y"};
673         BOOST_CHECK_EQUAL(x2 * y2, y2 * x2);
674     }
675     {
676         // Check with multiple threads.
677         settings::set_min_work_per_thread(1u);
678         using pt = p_type<rational>;
679         using mt = m_checker<pt>;
680         for (unsigned nt = 1u; nt <= 4u; ++nt) {
681             settings::set_n_threads(nt);
682             // Setup a multiplier for a polyomial with two variables and lcm 6.
683             auto tmp1 = pt{"x"} / 3 + pt{"y"}, tmp2 = pt{"y"} / 2 + pt{"x"};
684             mt m0{tmp1, tmp2};
685             // First let's try with an empty retval.
686             pt r;
687             r.set_symbol_set(symbol_set({symbol{"x"}, symbol{"y"}}));
688             BOOST_CHECK_NO_THROW(m0.finalise_series(r));
689             BOOST_CHECK_EQUAL(r, 0);
690             // Put in one term.
691             r += pt{"x"};
692             BOOST_CHECK_NO_THROW(m0.finalise_series(r));
693             BOOST_CHECK_EQUAL(r, pt{"x"} / 36);
694             // Put in another term.
695             r += 12 * pt{"y"};
696             BOOST_CHECK_NO_THROW(m0.finalise_series(r));
697             BOOST_CHECK_EQUAL(r, pt{"x"} / 36 + pt{"y"} / 3);
698         }
699     }
700     {
701         // Same as above, but with k monomial.
702         using pt = polynomial<rational, k_monomial>;
703         using mt = m_checker<pt>;
704         for (unsigned nt = 1u; nt <= 4u; ++nt) {
705             settings::set_n_threads(nt);
706             // Setup a multiplier for a polyomial with two variables and lcm 6.
707             auto tmp1 = pt{"x"} / 3 + pt{"y"}, tmp2 = pt{"y"} / 2 + pt{"x"};
708             mt m0{tmp1, tmp2};
709             // First let's try with an empty retval.
710             pt r;
711             r.set_symbol_set(symbol_set({symbol{"x"}, symbol{"y"}}));
712             BOOST_CHECK_NO_THROW(m0.finalise_series(r));
713             BOOST_CHECK_EQUAL(r, 0);
714             // Put in one term.
715             r += pt{"x"};
716             BOOST_CHECK_NO_THROW(m0.finalise_series(r));
717             BOOST_CHECK_EQUAL(r, pt{"x"} / 36);
718             // Put in another term.
719             r += 12 * pt{"y"};
720             BOOST_CHECK_NO_THROW(m0.finalise_series(r));
721             BOOST_CHECK_EQUAL(r, pt{"x"} / 36 + pt{"y"} / 3);
722         }
723     }
724     // Reset.
725     settings::reset_n_threads();
726     settings::reset_min_work_per_thread();
727 }
728