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