Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
polynomial_benchmark.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
37# include <algorithm>
38# include <chrono>
39# include <cmath>
40# include <cstdlib>
41# include <iomanip>
42# include <iostream>
43# include <limits>
44# include <numeric>
45# include <sstream>
46# include <stdexcept>
47# include <string>
48
49# include <tpl_polynomial.H>
50# include <tpl_multi_polynomial.H>
51
52using namespace Aleph;
53using Clock = std::chrono::steady_clock;
54
55namespace
56{
61
62 volatile long double benchmark_sink = 0;
63
64 struct Options
65 {
66 bool validate = false;
67 bool json = false;
68 size_t repetitions = 128;
69 size_t warmup = 2;
70 size_t samples = 7;
71 unsigned seed = 20260317u;
72 Array<size_t> sizes = {16, 64, 256, 1024};
73 };
74
75 struct TimingStats
76 {
77 double mean_us = 0.0;
78 double median_us = 0.0;
79 double min_us = 0.0;
80 double max_us = 0.0;
81 double stddev_us = 0.0;
82 };
83
84 struct BenchmarkRow
85 {
86 std::string case_name;
87 size_t size = 0;
88 size_t repetitions = 0;
89 TimingStats stats;
90 };
91
93 parse_sizes(const std::string &csv)
94 {
95 Array<size_t> sizes;
96 std::stringstream ss(csv);
97 std::string token;
98
99 while (std::getline(ss, token, ','))
100 {
101 if (token.empty())
102 continue;
103 sizes.append(static_cast<size_t>(std::stoull(token)));
104 }
105
106 return sizes;
107 }
108
109 [[nodiscard]] Options
110 parse_options(const int argc, char **argv)
111 {
112 Options options;
113
114 for (int i = 1; i < argc; ++i)
115 {
116 const std::string arg = argv[i];
117 if (arg == "--validate")
118 options.validate = true;
119 else if (arg == "--json")
120 options.json = true;
121 else if (arg == "--repetitions" and i + 1 < argc)
122 options.repetitions = static_cast<size_t>(std::stoull(argv[++i]));
123 else if (arg == "--warmup" and i + 1 < argc)
124 options.warmup = static_cast<size_t>(std::stoull(argv[++i]));
125 else if (arg == "--samples" and i + 1 < argc)
126 options.samples = static_cast<size_t>(std::stoull(argv[++i]));
127 else if (arg == "--seed" and i + 1 < argc)
128 options.seed = static_cast<unsigned>(std::stoul(argv[++i]));
129 else if (arg == "--sizes" and i + 1 < argc)
130 options.sizes = parse_sizes(argv[++i]);
131 else if (arg == "--help")
132 {
133 std::cout
134 << "Usage: polynomial_benchmark [--validate] [--json]"
135 << " [--repetitions N] [--warmup N] [--samples N]"
136 << " [--seed N] [--sizes a,b,c]\n";
137 std::exit(0);
138 }
139 else
140 {
141 std::cerr << "Unknown option: " << arg << "\n";
142 std::exit(1);
143 }
144 }
145
146 if (options.repetitions == 0 or options.warmup == 0 or options.samples == 0)
147 {
148 std::cerr << "Repetitions, warmup, and samples must be positive\n";
149 std::exit(1);
150 }
151
152 if (options.sizes.is_empty())
153 {
154 std::cerr << "At least one benchmark size is required\n";
155 std::exit(1);
156 }
157
158 return options;
159 }
160
161 [[nodiscard]] long long
162 deterministic_coeff(const unsigned seed, const size_t slot)
163 {
164 uint64_t x = static_cast<uint64_t>(seed) * 0x9E3779B185EBCA87ULL;
165 x += static_cast<uint64_t>(slot + 1) * 0xC2B2AE3D27D4EB4FULL;
166 x ^= x >> 33;
167 x *= 0xFF51AFD7ED558CCDULL;
168 x ^= x >> 33;
169 long long value = static_cast<long long>(x % 17ULL) - 8LL;
170 if (value >= 0)
171 ++value;
172 return value;
173 }
174
175 template <typename Poly>
176 [[nodiscard]] typename Poly::Coeff
177 manual_eval_univariate(const Poly &p, const typename Poly::Coeff &x)
178 {
179 using Coeff = typename Poly::Coeff;
180 Coeff result = Coeff{};
181 p.for_each_term([&result, &x](size_t exp, const Coeff &coeff)
182 {
183 result += coeff * polynomial_detail::power(x, exp);
184 });
185 return result;
186 }
187
189 make_sparse_real_poly(const size_t degree, const unsigned seed, const size_t variant)
190 {
191 RealPoly p;
192 const Array<size_t> exponents = {
193 0,
194 std::max<size_t>(1, degree / 7 + 1),
195 std::max<size_t>(2, degree / 3 + 1),
196 std::max<size_t>(3, (2 * degree) / 3 + 1),
197 degree
198 };
199
200 for (size_t i = 0; i < exponents.size(); ++i)
201 p.set_coeff(exponents(i),
202 static_cast<double>(deterministic_coeff(seed + static_cast<unsigned>(variant), i + variant * 11)));
203
204 return p;
205 }
206
208 make_sparse_int_poly(const size_t degree, const unsigned seed, const size_t variant)
209 {
210 IntPoly p;
211 const Array<size_t> exponents = {
212 0,
213 std::max<size_t>(1, degree / 5 + 1),
214 std::max<size_t>(2, degree / 2 + 1),
215 degree
216 };
217
218 for (size_t i = 0; i < exponents.size(); ++i)
219 p.set_coeff(exponents(i), deterministic_coeff(seed + static_cast<unsigned>(variant), i + variant * 7));
220
221 return p;
222 }
223
224 template <typename MultiPoly>
226 make_sparse_multi_poly(const size_t degree, const unsigned seed, const size_t variant)
227 {
228 MultiPoly p(2);
229
230 const Array<Array<size_t>> exponents = {
231 Array<size_t>{degree, 0},
232 Array<size_t>{0, degree},
233 Array<size_t>{std::max<size_t>(1, degree / 2), std::max<size_t>(1, degree / 3)},
234 Array<size_t>{std::max<size_t>(1, degree / 3), std::max<size_t>(1, degree / 2)},
235 Array<size_t>{1, 1},
236 Array<size_t>{0, 0}
237 };
238
239 for (size_t i = 0; i < exponents.size(); ++i)
240 p.add_to_coeff(exponents(i),
241 typename MultiPoly::Coeff(
242 deterministic_coeff(seed + static_cast<unsigned>(variant), i + variant * 13)));
243
244 return p;
245 }
246
248 make_groebner_system(const size_t size)
249 {
250 auto x = LexMultiPoly::variable(3, 0);
251 auto y = LexMultiPoly::variable(3, 1);
252 auto z = LexMultiPoly::variable(3, 2);
253
255 if (size <= 32)
256 {
257 gens.append(x * x - y);
258 gens.append(x * y - LexMultiPoly(3, 1.0));
259 return gens;
260 }
261
262 if (size <= 256)
263 {
264 gens.append(x * y - LexMultiPoly(3, 1.0));
265 gens.append(y - z);
266 gens.append(x - LexMultiPoly(3, 1.0));
267 return gens;
268 }
269
270 gens.append(x * y - z);
271 gens.append(y * z - LexMultiPoly(3, 1.0));
272 gens.append(x - z);
273 return gens;
274 }
275
276 template <typename Fn>
277 [[nodiscard]] TimingStats
278 measure(const Options &options, Fn &&fn)
279 {
280 Array<double> samples;
281 samples.reserve(options.samples);
282
283 for (size_t sample = 0; sample < options.samples; ++sample)
284 {
285 for (size_t warm = 0; warm < options.warmup; ++warm)
286 for (size_t rep = 0; rep < options.repetitions; ++rep)
287 fn();
288
289 const auto start = Clock::now();
290 for (size_t rep = 0; rep < options.repetitions; ++rep)
291 fn();
292 const auto end = Clock::now();
293
294 const double elapsed_us =
295 std::chrono::duration_cast<std::chrono::duration<double, std::micro>>(end - start).count();
296 samples.append(elapsed_us / static_cast<double>(options.repetitions));
297 }
298
299 std::sort(&samples[0], &samples[0] + samples.size());
300
301 TimingStats stats;
302 stats.min_us = samples(0);
303 stats.max_us = samples(samples.size() - 1);
304 stats.median_us = samples(samples.size() / 2);
305 stats.mean_us =
306 std::accumulate(&samples[0], &samples[0] + samples.size(), 0.0) / static_cast<double>(samples.size());
307
308 double variance = 0.0;
309 for (size_t i = 0; i < samples.size(); ++i)
310 {
311 const double diff = samples(i) - stats.mean_us;
312 variance += diff * diff;
313 }
314 variance /= static_cast<double>(samples.size());
315 stats.stddev_us = std::sqrt(variance);
316 return stats;
317 }
318
319 [[nodiscard]] bool
320 validate_suite(const Options &options)
321 {
322 bool ok = true;
323
324 for (size_t i = 0; i < options.sizes.size(); ++i)
325 {
326 const size_t degree = options.sizes(i);
327
328 RealPoly real_a = make_sparse_real_poly(degree, options.seed, 0);
329 RealPoly real_b = make_sparse_real_poly(degree, options.seed, 1);
330 const double x = 0.875;
331
332 if (std::abs(real_a.eval(x) - manual_eval_univariate(real_a, x)) > 1.0e-10)
333 {
334 std::cerr << "[validate] univariate eval mismatch at size " << degree << "\n";
335 ok = false;
336 }
337
338 RealPoly inner({0.0, 0.5});
339 RealPoly composed = real_a.compose(inner);
340 if (std::abs(composed.eval(0.5) - real_a.eval(inner.eval(0.5))) > 1.0e-10)
341 {
342 std::cerr << "[validate] univariate compose mismatch at size " << degree << "\n";
343 ok = false;
344 }
345
346 IntPoly int_a = make_sparse_int_poly(degree, options.seed, 0);
347 IntPoly int_b = make_sparse_int_poly(std::max<size_t>(3, degree / 4), options.seed, 1);
349 auto [q_uni, r_uni] = product.divmod(int_b);
350 if ((q_uni * int_b + r_uni) != product)
351 {
352 std::cerr << "[validate] univariate divmod identity mismatch at size " << degree << "\n";
353 ok = false;
354 }
355
356 IntMultiPoly multi_a = make_sparse_multi_poly<IntMultiPoly>(std::max<size_t>(2, degree / 8 + 2),
357 options.seed, 0);
358 IntMultiPoly multi_b = make_sparse_multi_poly<IntMultiPoly>(std::max<size_t>(2, degree / 10 + 2),
359 options.seed, 1);
362 auto [q_multi, r_multi] = multi_product.divmod(divisors);
363 if (((q_multi(0) * multi_b) + r_multi) != multi_product)
364 {
365 std::cerr << "[validate] multivariate divmod identity mismatch at size " << degree << "\n";
366 ok = false;
367 }
368
369 Array<LexMultiPoly> gb = LexMultiPoly::reduced_groebner_basis(make_groebner_system(degree));
370 for (size_t g = 0; g < gb.size(); ++g)
371 {
373 for (size_t h = 0; h < gb.size(); ++h)
374 if (g != h)
375 others.append(gb(h));
376 if (others.is_empty())
377 continue;
378 if (gb(g).divmod(others).second != gb(g))
379 {
380 std::cerr << "[validate] reduced Groebner basis not autoreduced at size "
381 << degree << "\n";
382 ok = false;
383 break;
384 }
385 }
386 }
387
388 return ok;
389 }
390
392 run_benchmarks(const Options &options)
393 {
395
396 for (size_t i = 0; i < options.sizes.size(); ++i)
397 {
398 const size_t size = options.sizes(i);
399
400 {
402 BenchmarkRow row;
403 row.case_name = "uni_eval_sparse";
404 row.size = size;
405 row.repetitions = options.repetitions;
406 row.stats = measure(options, [&p]()
407 {
408 benchmark_sink += p.eval(0.875);
409 });
410 rows.append(std::move(row));
411 }
412
413 {
416 BenchmarkRow row;
417 row.case_name = "uni_mul_sparse";
418 row.size = size;
419 row.repetitions = options.repetitions;
420 row.stats = measure(options, [&a, &b]()
421 {
422 RealPoly c = a * b;
423 benchmark_sink += static_cast<long double>(c.degree() + c.num_terms());
424 });
425 rows.append(std::move(row));
426 }
427
428 {
430 RealPoly inner({0.0, 0.5});
431 BenchmarkRow row;
432 row.case_name = "uni_compose_sparse";
433 row.size = size;
434 row.repetitions = options.repetitions;
435 row.stats = measure(options, [&outer, &inner]()
436 {
437 RealPoly c = outer.compose(inner);
438 benchmark_sink += static_cast<long double>(c.degree() + c.num_terms());
439 });
440 rows.append(std::move(row));
441 }
442
443 {
445 IntPoly b = make_sparse_int_poly(std::max<size_t>(3, size / 4), options.seed, 1);
446 IntPoly dividend = a * b;
447 BenchmarkRow row;
448 row.case_name = "uni_divmod_sparse_exact";
449 row.size = size;
450 row.repetitions = options.repetitions;
451 row.stats = measure(options, [&dividend, &b]()
452 {
453 auto [q, r] = dividend.divmod(b);
454 benchmark_sink += static_cast<long double>(q.degree() + r.degree());
455 });
456 rows.append(std::move(row));
457 }
458
459 {
460 const size_t multi_size = std::max<size_t>(2, size / 8 + 2);
463 BenchmarkRow row;
464 row.case_name = "multi_mul_sparse";
465 row.size = size;
466 row.repetitions = options.repetitions;
467 row.stats = measure(options, [&a, &b]()
468 {
469 IntMultiPoly c = a * b;
470 benchmark_sink += static_cast<long double>(c.degree() + c.num_terms());
471 });
472 rows.append(std::move(row));
473 }
474
475 {
476 const size_t multi_size = std::max<size_t>(2, size / 10 + 2);
479 IntMultiPoly dividend = a * b;
481 BenchmarkRow row;
482 row.case_name = "multi_divmod_exact";
483 row.size = size;
484 row.repetitions = options.repetitions;
485 row.stats = measure(options, [&dividend, &divisors]()
486 {
487 auto [q, r] = dividend.divmod(divisors);
488 benchmark_sink += static_cast<long double>(q(0).degree() + r.degree());
489 });
490 rows.append(std::move(row));
491 }
492
493 {
495 BenchmarkRow row;
496 row.case_name = "multi_groebner_reduced_lex";
497 row.size = size;
498 row.repetitions = std::max<size_t>(1, options.repetitions / 8);
499 const Options lighter = {false, false, row.repetitions, options.warmup, options.samples, options.seed,
501 row.stats = measure(lighter, [&gens]()
502 {
503 Array<LexMultiPoly> gb = LexMultiPoly::reduced_groebner_basis(gens);
504 benchmark_sink += static_cast<long double>(gb.size());
505 });
506 rows.append(std::move(row));
507 }
508 }
509
510 return rows;
511 }
512
513 void
514 print_rows_json(const Array<BenchmarkRow> &rows, const Options &options)
515 {
516 std::cout << "{\n"
517 << " \"mode\": \"benchmark\",\n"
518 << " \"metadata\": {\n"
519 << " \"seed\": " << options.seed << ",\n"
520 << " \"repetitions\": " << options.repetitions << ",\n"
521 << " \"warmup\": " << options.warmup << ",\n"
522 << " \"samples\": " << options.samples << "\n"
523 << " },\n"
524 << " \"rows\": [\n";
525
526 for (size_t i = 0; i < rows.size(); ++i)
527 {
528 const BenchmarkRow &row = rows(i);
529 std::cout << " {\n"
530 << " \"case\": \"" << row.case_name << "\",\n"
531 << " \"size\": " << row.size << ",\n"
532 << " \"repetitions\": " << row.repetitions << ",\n"
533 << " \"mean_us\": " << std::setprecision(12) << row.stats.mean_us << ",\n"
534 << " \"median_us\": " << row.stats.median_us << ",\n"
535 << " \"min_us\": " << row.stats.min_us << ",\n"
536 << " \"max_us\": " << row.stats.max_us << ",\n"
537 << " \"stddev_us\": " << row.stats.stddev_us << "\n"
538 << " }" << (i + 1 == rows.size() ? "\n" : ",\n");
539 }
540
541 std::cout << " ]\n"
542 << "}\n";
543 }
544
545 void
547 {
548 std::cout << "Polynomial benchmark (microseconds per call)\n"
549 << std::left << std::setw(28) << "case"
550 << std::setw(8) << "size"
551 << std::setw(12) << "median"
552 << std::setw(12) << "mean"
553 << std::setw(12) << "min"
554 << std::setw(12) << "max"
555 << "stddev\n";
556
557 for (size_t i = 0; i < rows.size(); ++i)
558 {
559 const BenchmarkRow &row = rows(i);
560 std::cout << std::left << std::setw(28) << row.case_name
561 << std::setw(8) << row.size
562 << std::setw(12) << std::fixed << std::setprecision(3) << row.stats.median_us
563 << std::setw(12) << row.stats.mean_us
564 << std::setw(12) << row.stats.min_us
565 << std::setw(12) << row.stats.max_us
566 << row.stats.stddev_us << "\n";
567 }
568
569 std::cout << "Benchmark sink: " << static_cast<long double>(benchmark_sink) << "\n";
570 }
571}
572
573int
574main(int argc, char **argv)
575{
576 try
577 {
578 const Options options = parse_options(argc, argv);
579
580 if (options.validate)
581 {
582 const bool ok = validate_suite(options);
583 if (not ok)
584 return 1;
585 if (not options.json)
586 std::cout << "polynomial_benchmark: validation passed\n";
587 }
588
590 if (options.json)
592 else
593 print_rows_table(rows);
594
595 return 0;
596 }
597 catch (const std::exception &e)
598 {
599 std::cerr << "polynomial_benchmark: " << e.what() << "\n";
600 return 1;
601 }
602}
long double h
Definition btreepic.C:154
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:351
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:245
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:315
Sparse multivariate polynomial.
Univariate polynomial over a generic coefficient ring.
std::pair< Gen_Polynomial, Gen_Polynomial > divmod(const Gen_Polynomial &d) const
Polynomial long division: returns (quotient, remainder).
void set_coeff(size_t exp, const Coefficient &c)
Set coefficient at exponent; removes entry if zero.
std::chrono::steady_clock Clock
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_exp_function > > exp(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4066
static mpfr_t y
Definition mpfr_mul_d.c:3
C power(C base, size_t exp)
Fast exponentiation by squaring.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
size_t size(Node *root) noexcept
auto variance(const Container &data, bool population=false) -> std::decay_t< decltype(*std::begin(data))>
Compute variance using Welford's numerically stable algorithm.
Definition stat_utils.H:215
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
DynList< T > rep(const size_t n, const T &item)
Create a sequence of repeated items.
T product(const Container &container, const T &init=T{1})
Compute product of all elements.
bool diff(const C1 &c1, const C2 &c2, Eq e=Eq())
Check if two containers differ.
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
static struct argp_option options[]
Definition ntreepic.C:1886
void run_benchmarks(const BenchmarkConfig &config)
ValueArg< size_t > seed
Definition testHash.C:48
gsl_rng * r
Sparse multivariate polynomial over an arbitrary coefficient ring.
Univariate polynomial ring arithmetic over generic coefficients.