Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
tpl_polynomial.H
Go to the documentation of this file.
1
2/*
3 Aleph_w
4
5 Data structures & Algorithms
6 version 2.0.0b
7 https://github.com/lrleon/Aleph-w
8
9 This file is part of Aleph-w library
10
11 Copyright (c) 2002-2026 Leandro Rabindranath Leon
12
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30*/
31
57#ifndef TPL_POLYNOMIAL_H
58#define TPL_POLYNOMIAL_H
59
60#include <cmath>
61#include <initializer_list>
62#include <limits>
63#include <sstream>
64#include <string>
65#include <type_traits>
66#include <utility>
67#include <numeric> // std::gcd
68#include <random> // std::mt19937_64
69
70#include <ah-errors.H>
71#include <tpl_dynMapTree.H>
72#include <tpl_array.H>
73#include <htlist.H>
74#include <modular_arithmetic.H> // mod_inv, mod_mul
75
76namespace Aleph {
77
79namespace polynomial_detail {
80
86template <typename C>
87C power(C base, size_t exp)
88{
89 C result = C(1);
90 while (exp > 0)
91 {
92 if (exp & 1)
93 result = result * base;
94 base = base * base;
95 exp >>= 1;
96 }
97 return result;
98}
99
100template <typename Int>
101 requires std::is_integral_v<Int>
103{
104 if constexpr (std::is_signed_v<Int>)
105 {
106 using Unsigned = std::make_unsigned_t<Int>;
107 const Unsigned magnitude =
108 value < Int{} ? Unsigned{} - static_cast<Unsigned>(value) : static_cast<Unsigned>(value);
109 return static_cast<uint64_t>(magnitude);
110 }
111 else
112 return static_cast<uint64_t>(value);
113}
114
115template <typename Int>
116 requires std::is_integral_v<Int>
118{
119 if (modulus == 0)
120 return 0;
121
122 if constexpr (std::is_signed_v<Int>)
123 {
124 const auto mod_i64 = static_cast<int64_t>(modulus);
125 auto rem = static_cast<int64_t>(value % static_cast<Int>(mod_i64));
126 if (rem < 0)
127 rem += mod_i64;
128 return static_cast<uint64_t>(rem);
129 }
130 else
131 return static_cast<uint64_t>(value % static_cast<Int>(modulus));
132}
133
134template <typename Int>
135 requires std::is_integral_v<Int>
137{
138 if constexpr (std::is_signed_v<Int>)
139 {
140 const uint64_t canonical = modulus == 0 ? 0 : value % modulus;
141 const uint64_t half = modulus / 2;
142 if (canonical > half)
143 return static_cast<Int>(static_cast<int64_t>(canonical) - static_cast<int64_t>(modulus));
144 return static_cast<Int>(canonical);
145 }
146 else
147 return static_cast<Int>(modulus == 0 ? 0 : value % modulus);
148}
149
150} // end namespace polynomial_detail
151
165template <typename Coefficient = double>
167{
168public:
170
171private:
172 using Term = std::pair<size_t, Coefficient>;
174
175 // --- Epsilon-based zero detection for floating-point types ----------
176
182 static constexpr Coefficient epsilon() noexcept
183 {
184 if constexpr (std::is_floating_point_v<Coefficient>)
185 return Coefficient(128) * std::numeric_limits<Coefficient>::epsilon();
186 else
187 return Coefficient{};
188 }
189
195 static bool coeff_is_zero(const Coefficient &c) noexcept
196 {
197 if constexpr (std::is_floating_point_v<Coefficient>)
198 return std::abs(c) <= epsilon();
199 else
200 return c == Coefficient{};
201 }
202
205 {
206 DynList<size_t> zeros;
207 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
208 {
209 auto &p = it.get_curr();
210 if (coeff_is_zero(p.second))
211 zeros.append(p.first);
212 }
213 zeros.for_each([this](size_t e)
214 {
215 coeffs.remove_key(e);
216 });
217 }
218
221 {
222 auto *p = coeffs.search(exp);
223 return p != nullptr ? p->second : Coefficient{};
224 }
225
227 Coefficient *coeff_ptr(size_t exp) noexcept
228 {
229 auto *p = coeffs.search(exp);
230 return p != nullptr ? &p->second : nullptr;
231 }
232
234 const Coefficient *coeff_ptr(size_t exp) const noexcept
235 {
236 auto *p = coeffs.search(exp);
237 return p != nullptr ? &p->second : nullptr;
238 }
239
241 void add_to_coeff(size_t exp, const Coefficient &delta)
242 {
243 if (coeff_is_zero(delta))
244 return;
245
246 if (auto *slot = coeff_ptr(exp); slot != nullptr)
247 {
248 *slot = *slot + delta;
249 if (coeff_is_zero(*slot))
250 coeffs.remove_key(exp);
251 }
252 else
253 coeffs.insert(exp, delta);
254 }
255
257 void add_scaled_shifted(const Gen_Polynomial &src, const Coefficient &scale, size_t shift = 0)
258 {
259 if (coeff_is_zero(scale) or src.is_zero())
260 return;
261
262 src.for_each_term([this, &scale, shift](size_t exp, const Coefficient &c)
263 {
264 add_to_coeff(exp + shift, c * scale);
265 });
266 }
267
270 {
271 if (coeffs.is_empty())
272 return;
273
274 if (coeff_is_zero(s))
275 {
277 return;
278 }
279
280 if (s == Coefficient(1))
281 return;
282
283 DynList<size_t> zeros;
284 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
285 {
286 auto &p = it.get_curr();
287 p.second = p.second * s;
288 if (coeff_is_zero(p.second))
289 zeros.append(p.first);
290 }
291
292 zeros.for_each([this](size_t exp)
293 {
294 coeffs.remove_key(exp);
295 });
296 }
297
300 {
301 ah_domain_error_if(coeff_is_zero(s)) << "polynomial division by zero scalar";
302
303 if (coeffs.is_empty())
304 return;
305
306 if (s == Coefficient(1))
307 return;
308
309 DynList<size_t> zeros;
310 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
311 {
312 auto &p = it.get_curr();
313 p.second = p.second / s;
314 if (coeff_is_zero(p.second))
315 zeros.append(p.first);
316 }
317
318 zeros.for_each([this](size_t exp)
319 {
320 coeffs.remove_key(exp);
321 });
322 }
323
325 template <class Op>
326 void for_each_term_desc(Op &&op) const
327 {
329 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
330 {
331 const auto &p = it.get_curr();
332 desc_terms.append(&p);
333 }
334
335 for (size_t i = desc_terms.size(); i > 0; --i)
336 {
337 const Term &term = *desc_terms(i - 1);
338 op(term.first, term.second);
339 }
340 }
341
344 {
345 if (is_zero())
346 return Gen_Polynomial();
347
348 Gen_Polynomial result;
349 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
350 {
351 auto &p = it.get_curr();
352 result.add_to_coeff(p.first, p.second * c);
353 result.add_to_coeff(p.first + 1, p.second);
354 }
355 return result;
356 }
357
358public:
359 // =================================================================
360 // Construction
361 // =================================================================
362
364 Gen_Polynomial() = default;
365
369 explicit Gen_Polynomial(const Coefficient &c)
370 {
371 if (not coeff_is_zero(c))
372 coeffs[0] = c;
373 }
374
380 {
381 if (not coeff_is_zero(c))
382 coeffs[exp] = c;
383 }
384
392 Gen_Polynomial(std::initializer_list<Coefficient> il)
393 {
394 size_t exp = 0;
395 for (auto &c : il)
396 {
397 if (not coeff_is_zero(c))
398 coeffs[exp] = c;
399 ++exp;
400 }
401 }
402
410 {
411 size_t exp = 0;
412 l.for_each([this, &exp](const Coefficient &c)
413 {
414 if (not coeff_is_zero(c))
415 coeffs[exp] = c;
416 ++exp;
417 });
418 }
419
427 explicit Gen_Polynomial(const DynList<std::pair<size_t, Coefficient>> &term_list)
428 {
429 term_list.for_each([this](const Term &t)
430 {
431 if (not coeff_is_zero(t.second))
432 add_to_coeff(t.first, t.second);
433 });
434 }
435
436 // =================================================================
437 // Static factory methods
438 // =================================================================
439
442 {
443 return Gen_Polynomial();
444 }
445
448 {
449 return Gen_Polynomial(Coefficient(1));
450 }
451
455 [[nodiscard]] static Gen_Polynomial x_to(size_t n)
456 {
457 return Gen_Polynomial(Coefficient(1), n);
458 }
459
470 {
471 Gen_Polynomial result(Coefficient(1)); // start with constant 1
472 roots.for_each([&result](const Coefficient &r)
473 {
474 result = result.multiply_by_monic_linear(-r); // (x - r)
475 });
476 return result;
477 }
478
494 const DynList<std::pair<Coefficient, Coefficient>> &points)
495 {
496 ah_domain_error_if(points.is_empty()) << "interpolate requires at least one point";
497
498 const size_t n = points.size();
501 xs.putn(n);
502 dd.putn(n);
503
504 size_t idx = 0;
505 points.for_each([&](const std::pair<Coefficient, Coefficient> &pt)
506 {
507 xs[idx] = pt.first;
508 dd[idx] = pt.second;
509 ++idx;
510 });
511
512 for (size_t i = 0; i < n; ++i)
513 for (size_t j = i + 1; j < n; ++j)
514 ah_domain_error_if(coeff_is_zero(xs[i] - xs[j])) << "interpolate: duplicate x-values";
515
516 for (size_t order = 1; order < n; ++order)
517 for (size_t i = n - 1; i >= order; --i)
518 {
519 Coefficient denom = xs[i] - xs[i - order];
520 dd[i] = (dd[i] - dd[i - 1]) / denom;
521 if (i == order)
522 break;
523 }
524
525 Gen_Polynomial result(dd[0]);
527 for (size_t i = 1; i < n; ++i)
528 {
529 basis = basis.multiply_by_monic_linear(-xs[i - 1]);
530 if (not coeff_is_zero(dd[i]))
531 result.add_scaled_shifted(basis, dd[i]);
532 }
533
534 return result;
535 }
536
537 // =================================================================
538 // Properties
539 // =================================================================
540
543 {
544 return coeffs.is_empty();
545 }
546
553 {
554 if (coeffs.is_empty())
555 return 0;
556 return coeffs.max().first;
557 }
558
561 {
562 return coeffs.size();
563 }
564
567 {
568 return coeffs.is_empty() or (coeffs.size() == 1 and coeffs.min().first == 0);
569 }
570
573 {
574 return coeffs.size() == 1;
575 }
576
579 {
580 return not coeffs.is_empty() and coeff_is_zero(coeffs.max().second - Coefficient(1));
581 }
582
583 // =================================================================
584 // Coefficient access
585 // =================================================================
586
589 {
590 return coeff_at(exp);
591 }
592
597 {
598 if (coeffs.is_empty())
599 return Coefficient{};
600 return coeffs.max().second;
601 }
602
604 [[nodiscard]] bool has_term(size_t exp) const noexcept
605 {
606 return coeffs.has(exp);
607 }
608
609 // =================================================================
610 // Evaluation
611 // =================================================================
612
623 {
624 if (coeffs.is_empty())
625 return Coefficient{};
626
627 if (x == Coefficient{})
628 return coeff_at(0);
629
630 Coefficient result = Coefficient{};
631 size_t prev_exp = 0;
632 bool first = true;
633
634 for_each_term_desc([&](size_t exp, const Coefficient &c)
635 {
636 if (first)
637 {
638 result = c;
639 prev_exp = exp;
640 first = false;
641 return;
642 }
643
644 result = result * polynomial_detail::power(x, prev_exp - exp) + c;
645 prev_exp = exp;
646 });
647
648 if (prev_exp > 0)
649 result = result * polynomial_detail::power(x, prev_exp);
650
651 return result;
652 }
653
664 {
665 Coefficient result = Coefficient{};
666 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
667 {
668 auto &p = it.get_curr();
669 result = result + p.second * polynomial_detail::power(x, p.first);
670 }
671 return result;
672 }
673
683 {
684 if (coeffs.is_empty())
685 return Coefficient{};
686
687 if (x == Coefficient{})
688 return coeff_at(0);
689
690 if (num_terms() <= 2)
691 return sparse_eval(x);
692
693 return horner_eval(x);
694 }
695
698 {
699 return eval(x);
700 }
701
712 {
714 points.for_each([this, &results](const Coefficient &x)
715 {
716 results.append(eval(x));
717 });
718 return results;
719 }
720
721 // =================================================================
722 // Arithmetic operators
723 // =================================================================
724
727 {
728 Gen_Polynomial result;
729 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
730 {
731 auto &p = it.get_curr();
732 result.coeffs[p.first] = -p.second;
733 }
734 return result;
735 }
736
739 {
740 Gen_Polynomial result = *this;
741 result += q;
742 return result;
743 }
744
747 {
748 if (this == &q)
749 {
751 return *this;
752 }
753
754 for (auto it = q.coeffs.get_it(); it.has_curr(); it.next_ne())
755 {
756 auto &p = it.get_curr();
757 add_to_coeff(p.first, p.second);
758 }
759 return *this;
760 }
761
764 {
765 Gen_Polynomial result = *this;
766 result += c;
767 return result;
768 }
769
772 {
773 add_to_coeff(0, c);
774 return *this;
775 }
776
779 {
780 Gen_Polynomial result = *this;
781 result -= q;
782 return result;
783 }
784
787 {
788 if (this == &q)
789 {
791 return *this;
792 }
793
794 for (auto it = q.coeffs.get_it(); it.has_curr(); it.next_ne())
795 {
796 auto &p = it.get_curr();
797 add_to_coeff(p.first, -p.second);
798 }
799 return *this;
800 }
801
804 {
805 Gen_Polynomial result = *this;
806 result -= c;
807 return result;
808 }
809
812 {
813 add_to_coeff(0, -c);
814 return *this;
815 }
816
823 {
824 if (is_zero() or q.is_zero())
825 return Gen_Polynomial();
826
827 Gen_Polynomial result;
828 const Gen_Polynomial *outer = this;
829 const Gen_Polynomial *inner = &q;
830
831 if (q.num_terms() < num_terms())
832 {
833 outer = &q;
834 inner = this;
835 }
836
837 for (auto it1 = outer->coeffs.get_it(); it1.has_curr(); it1.next_ne())
838 {
839 auto &t1 = it1.get_curr();
840 for (auto it2 = inner->coeffs.get_it(); it2.has_curr(); it2.next_ne())
841 {
842 auto &t2 = it2.get_curr();
843 result.add_to_coeff(t1.first + t2.first, t1.second * t2.second);
844 }
845 }
846 return result;
847 }
848
851 {
852 *this = *this * q;
853 return *this;
854 }
855
858 {
859 if (coeff_is_zero(s))
860 return Gen_Polynomial();
861 Gen_Polynomial result;
862 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
863 {
864 auto &p = it.get_curr();
865 Coefficient prod = p.second * s;
867 result.coeffs[p.first] = prod;
868 }
869 return result;
870 }
871
874 {
875 scale_inplace(s);
876 return *this;
877 }
878
883 {
884 ah_domain_error_if(coeff_is_zero(s)) << "polynomial division by zero scalar";
885 Gen_Polynomial result;
886 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
887 {
888 auto &p = it.get_curr();
889 Coefficient q = p.second / s;
890 if (not coeff_is_zero(q))
891 result.coeffs[p.first] = q;
892 }
893 return result;
894 }
895
898 {
900 return *this;
901 }
902
903 // =================================================================
904 // Polynomial division
905 // =================================================================
906
917 [[nodiscard]] std::pair<Gen_Polynomial, Gen_Polynomial> divmod(const Gen_Polynomial &d) const
918 {
919 ah_domain_error_if(d.is_zero()) << "polynomial division by zero";
920
921 if (is_zero() or degree() < d.degree())
922 return {Gen_Polynomial(), *this};
923
924 Gen_Polynomial q; // quotient
925 Gen_Polynomial r = *this; // remainder
926 const size_t d_degree = d.degree();
927 const Coefficient d_lc = d.leading_coeff();
928
929 while (not r.is_zero())
930 {
931 const size_t r_degree = r.degree();
932 if (r_degree < d_degree)
933 break;
934
935 const Coefficient scale = r.leading_coeff() / d_lc;
937 << "divmod: inexact coefficient division (leading_coeff=" << r.leading_coeff()
938 << " / divisor_lc=" << d_lc << " = 0). "
939 << "For integral/non-field coefficients, use pseudo_divmod() instead.";
940 const size_t shift = r_degree - d_degree;
941
943 r.add_scaled_shifted(d, -scale, shift);
944 }
945
946 return {q, r};
947 }
948
951 {
952 return divmod(d).first;
953 }
954
957 {
958 return divmod(d).second;
959 }
960
963 {
964 *this = divmod(d).first;
965 return *this;
966 }
967
970 {
971 *this = divmod(d).second;
972 return *this;
973 }
974
975 // =================================================================
976 // Calculus
977 // =================================================================
978
985 {
986 Gen_Polynomial result;
987 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
988 {
989 auto &p = it.get_curr();
990 if (p.first == 0)
991 continue; // derivative of constant is 0
992 Coefficient dc = p.second * Coefficient(p.first);
993 if (not coeff_is_zero(dc))
994 result.coeffs[p.first - 1] = dc;
995 }
996 return result;
997 }
998
1011 {
1012 Gen_Polynomial result = *this;
1013 for (size_t i = 0; i < n and not result.is_zero(); ++i)
1014 result = result.derivative();
1015 return result;
1016 }
1017
1029 {
1030 Gen_Polynomial result;
1031 if (not coeff_is_zero(C))
1032 result.coeffs[0] = C;
1033 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1034 {
1035 auto &p = it.get_curr();
1036 Coefficient ic = p.second / Coefficient(p.first + 1);
1037 if (not coeff_is_zero(ic))
1038 result.coeffs[p.first + 1] = ic;
1039 }
1040 return result;
1041 }
1042
1053 {
1054 Coefficient result = Coefficient{};
1055
1056 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1057 {
1058 auto &p = it.get_curr();
1059 const size_t next_exp = p.first + 1;
1060 const Coefficient scale = p.second / Coefficient(next_exp);
1061 if (coeff_is_zero(scale))
1062 continue;
1063
1064 result = result +
1066 }
1067
1068 return result;
1069 }
1070
1071 // =================================================================
1072 // Composition and power
1073 // =================================================================
1074
1082 {
1083 if (is_zero())
1084 return Gen_Polynomial();
1085
1086 if (q.is_constant())
1087 return Gen_Polynomial(eval(q[0]));
1088
1089 Gen_Polynomial result;
1090 size_t prev_exp = 0;
1091 bool first = true;
1092
1093 for_each_term_desc([&](size_t exp, const Coefficient &c)
1094 {
1095 if (first)
1096 {
1097 result = Gen_Polynomial(c);
1098 prev_exp = exp;
1099 first = false;
1100 return;
1101 }
1102
1103 const size_t gap = prev_exp - exp;
1104 if (gap == 1)
1105 result *= q;
1106 else if (gap > 1)
1107 result *= q.pow(gap);
1108
1109 result.add_to_coeff(0, c);
1110 prev_exp = exp;
1111 });
1112
1113 if (prev_exp == 1)
1114 result *= q;
1115 else if (prev_exp > 1)
1116 result *= q.pow(prev_exp);
1117
1118 return result;
1119 }
1120
1126 [[nodiscard]] Gen_Polynomial pow(size_t n) const
1127 {
1128 Gen_Polynomial result(Coefficient(1));
1129 Gen_Polynomial base = *this;
1130 while (n > 0)
1131 {
1132 if (n & 1)
1133 result *= base;
1134 base *= base;
1135 n >>= 1;
1136 }
1137 return result;
1138 }
1139
1140 // =================================================================
1141 // GCD
1142 // =================================================================
1143
1154 {
1155 while (not b.is_zero())
1156 {
1157 Gen_Polynomial r = a % b;
1158 a = b;
1159 b = r;
1160 }
1161 // Make monic
1162 if (not a.is_zero())
1163 a /= a.leading_coeff();
1164 return a;
1165 }
1166
1182
1184 {
1187
1188 while (not b.is_zero())
1189 {
1190 auto [q, r] = a.divmod(b);
1191 a = b;
1192 b = r;
1193 Gen_Polynomial s2 = s0 - q * s1;
1194 Gen_Polynomial t2 = t0 - q * t1;
1195 s0 = s1;
1196 s1 = s2;
1197 t0 = t1;
1198 t1 = t2;
1199 }
1200
1201 if (not a.is_zero())
1202 {
1204 a /= lc;
1205 s0 /= lc;
1206 t0 /= lc;
1207 }
1208
1209 return {a, s0, t0};
1210 }
1211
1221 {
1222 if (a.is_zero() or b.is_zero())
1223 return Gen_Polynomial();
1224 Gen_Polynomial g = gcd(a, b);
1225 return a / g * b;
1226 }
1227
1239 [[nodiscard]] std::pair<Gen_Polynomial, Gen_Polynomial> pseudo_divmod(const Gen_Polynomial &d) const
1240 {
1241 ah_domain_error_if(d.is_zero()) << "pseudo-division by zero polynomial";
1242
1243 if (is_zero() or degree() < d.degree())
1244 return {Gen_Polynomial(), *this};
1245
1246 const Coefficient lc_d = d.leading_coeff();
1247 const size_t d_degree = d.degree();
1248 size_t delta = degree() - d_degree + 1;
1249
1251 Gen_Polynomial r = *this;
1252
1253 while (not r.is_zero())
1254 {
1255 const size_t r_degree = r.degree();
1256 if (r_degree < d_degree)
1257 break;
1258
1259 const Coefficient lc_r = r.leading_coeff();
1260 const size_t exp = r_degree - d_degree;
1261
1262 // Multiply q and r by lc(d) to avoid division
1264 r.scale_inplace(lc_d);
1265 --delta;
1266
1267 q.add_to_coeff(exp, lc_r);
1268 r.add_scaled_shifted(d, -lc_r, exp);
1269 }
1270
1271 // Multiply by remaining powers of lc(d)
1272 if (delta > 0)
1273 {
1276 r.scale_inplace(scale);
1277 }
1278
1279 return {q, r};
1280 }
1281
1282 // =================================================================
1283 // Algebraic transformations
1284 // =================================================================
1285
1292 {
1293 ah_domain_error_if(is_zero()) << "cannot make zero polynomial monic";
1294 return *this / leading_coeff();
1295 }
1296
1303 {
1304 if (is_zero())
1305 return Gen_Polynomial();
1306 size_t d = degree();
1307 Gen_Polynomial result;
1308 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1309 {
1310 auto &p = it.get_curr();
1311 result.coeffs[d - p.first] = p.second;
1312 }
1313 return result;
1314 }
1315
1321 {
1322 Gen_Polynomial result;
1323 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1324 {
1325 auto &p = it.get_curr();
1326 result.coeffs[p.first] = (p.first % 2 == 0) ? p.second : -p.second;
1327 }
1328 return result;
1329 }
1330
1342 {
1343 return compose(Gen_Polynomial({k, Coefficient(1)}));
1344 }
1345
1352 {
1353 if (k == 0)
1354 return *this;
1355 Gen_Polynomial result;
1356 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1357 {
1358 auto &p = it.get_curr();
1359 result.coeffs[p.first + k] = p.second;
1360 }
1361 return result;
1362 }
1363
1372 {
1373 if (k == 0)
1374 return *this;
1375 Gen_Polynomial result;
1376 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1377 {
1378 auto &p = it.get_curr();
1379 if (p.first >= k)
1380 result.coeffs[p.first - k] = p.second;
1381 }
1382 return result;
1383 }
1384
1393 {
1394 Gen_Polynomial result;
1395 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1396 {
1397 auto &p = it.get_curr();
1398 if (p.first < n)
1399 result.coeffs[p.first] = p.second;
1400 }
1401 return result;
1402 }
1403
1411 {
1412 if (is_zero())
1413 return Array<Coefficient>();
1414 size_t d = degree();
1415 Array<Coefficient> result(d + 1, Coefficient{});
1416 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1417 {
1418 auto &p = it.get_curr();
1419 result(p.first) = p.second;
1420 }
1421 return result;
1422 }
1423
1437 {
1438 if (is_zero() or is_constant())
1439 return *this;
1440 Gen_Polynomial g = gcd(*this, derivative());
1441 return *this / g;
1442 }
1443
1444 // =================================================================
1445 // Root analysis (floating-point coefficients)
1446 // =================================================================
1447
1457 {
1458 ah_domain_error_if(is_zero()) << "cauchy_bound: zero polynomial has no roots";
1459 if (is_constant())
1460 return Coefficient{};
1461
1462 const size_t d = degree();
1463 const Coefficient lc = leading_coeff();
1464 if constexpr (std::is_floating_point_v<Coefficient>)
1465 {
1467 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1468 {
1469 auto &p = it.get_curr();
1470 if (p.first == d)
1471 continue;
1472 const Coefficient ratio = std::abs(p.second / lc);
1473 if (ratio > max_ratio)
1474 max_ratio = ratio;
1475 }
1476 return Coefficient(1) + max_ratio;
1477 }
1478 else
1479 {
1480 long double max_ratio_ld = 0.0L;
1481 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1482 {
1483 auto &p = it.get_curr();
1484 if (p.first == d)
1485 continue;
1486 const long double ratio =
1487 std::fabs(static_cast<long double>(p.second) / static_cast<long double>(lc));
1488 if (ratio > max_ratio_ld)
1489 max_ratio_ld = ratio;
1490 }
1491 return Coefficient(1) + static_cast<Coefficient>(std::ceil(max_ratio_ld));
1492 }
1493 }
1494
1503 [[nodiscard]] size_t sign_variations() const
1504 {
1505 if (coeffs.size() < 2)
1506 return 0;
1507
1508 size_t changes = 0;
1509 bool have_prev = false;
1510 bool prev_positive = false;
1511
1512 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1513 {
1514 auto &p = it.get_curr();
1515 bool positive;
1516 if constexpr (std::is_floating_point_v<Coefficient>)
1517 positive = p.second > epsilon();
1518 else
1519 positive = p.second > Coefficient{};
1520
1521 bool is_neg;
1522 if constexpr (std::is_floating_point_v<Coefficient>)
1523 is_neg = p.second < -epsilon();
1524 else
1525 is_neg = p.second < Coefficient{};
1526
1528 continue; // skip zero coefficients
1529
1531 ++changes;
1533 have_prev = true;
1534 }
1535 return changes;
1536 }
1537
1549 {
1551 chain.append(*this);
1552 if (is_zero() or is_constant())
1553 return chain;
1554
1555 Gen_Polynomial p0 = *this;
1557 chain.append(p1);
1558
1559 while (not p1.is_zero())
1560 {
1561 Gen_Polynomial r = -(p0 % p1);
1562 if (r.is_zero())
1563 break;
1564 chain.append(r);
1565 p0 = p1;
1566 p1 = r;
1567 }
1568
1569 return chain;
1570 }
1571
1579 const Coefficient &x)
1580 {
1581 size_t changes = 0;
1582 bool have_prev = false;
1583 bool prev_positive = false;
1584
1585 chain.for_each([&](const Gen_Polynomial &p)
1586 {
1587 Coefficient val = p(x);
1588 if (coeff_is_zero(val))
1589 return; // skip zeros in Sturm evaluation
1590
1591 bool positive = val > Coefficient{};
1593 ++changes;
1595 have_prev = true;
1596 });
1597
1598 return changes;
1599 }
1600
1614 [[nodiscard]] size_t count_real_roots(const Coefficient &a, const Coefficient &b) const
1615 {
1616 if (b < a)
1617 return count_real_roots(b, a);
1618
1619 if (is_zero())
1620 return 0;
1621
1622 auto chain = sturm_chain();
1623 size_t sa = sturm_sign_changes(chain, a);
1624 size_t sb = sturm_sign_changes(chain, b);
1625 size_t count = sa > sb ? sa - sb : 0;
1626
1627 // Sturm's theorem counts roots in (a, b]; add root at a if present
1628 if (coeff_is_zero((*this)(a)))
1629 ++count;
1630
1631 return count;
1632 }
1633
1640 {
1641 if (is_zero() or is_constant())
1642 return 0;
1643 Coefficient bound = cauchy_bound();
1644 return count_real_roots(-bound, bound);
1645 }
1646
1661 Coefficient b,
1662 Coefficient tol = Coefficient(1e-12),
1663 size_t max_iter = 200) const
1664 {
1665 if (b < a)
1666 std::swap(a, b);
1667
1668 Coefficient fa = eval(a), fb = eval(b);
1669 if (coeff_is_zero(fa))
1670 return a;
1671 if (coeff_is_zero(fb))
1672 return b;
1673
1676 << "bisect_root: f(a) and f(b) must have opposite signs";
1677
1678 for (size_t i = 0; i < max_iter; ++i)
1679 {
1680 Coefficient mid = (a + b) / Coefficient(2);
1681 if (b - a < tol)
1682 return mid;
1684 if (coeff_is_zero(fm))
1685 return mid;
1686 if (fa > Coefficient{} == fm > Coefficient{})
1687 {
1688 a = mid;
1689 fa = fm;
1690 }
1691 else
1692 {
1693 b = mid;
1694 fb = fm;
1695 }
1696 }
1697 return (a + b) / Coefficient(2);
1698 }
1699
1710 Coefficient tol = Coefficient(1e-12),
1711 size_t max_iter = 100) const
1713 {
1715
1716 for (size_t i = 0; i < max_iter; ++i)
1717 {
1718 Coefficient fx = eval(x0);
1719 if (coeff_is_zero(fx))
1720 return x0;
1721 Coefficient dfx = dp.eval(x0);
1722 ah_domain_error_if(coeff_is_zero(dfx)) << "newton_root: derivative is zero at x = " << x0;
1723 Coefficient x1 = x0 - fx / dfx;
1724 Coefficient delta = x1 - x0;
1725 if constexpr (std::is_floating_point_v<Coefficient>)
1726 {
1727 if (std::abs(delta) < tol)
1728 return x1;
1729 }
1730 else
1731 {
1732 if (delta == Coefficient{})
1733 return x1;
1734 }
1735 x0 = x1;
1736 }
1737 return x0;
1738 }
1739
1740 // =================================================================
1741 // Comparison
1742 // =================================================================
1743
1749 [[nodiscard]] bool operator == (const Gen_Polynomial &q) const noexcept
1750 {
1751 if (this == &q)
1752 return true;
1753
1754 auto it1 = coeffs.get_it();
1755 auto it2 = q.coeffs.get_it();
1756
1757 while (it1.has_curr() and it2.has_curr())
1758 {
1759 auto &lhs = it1.get_curr();
1760 auto &rhs = it2.get_curr();
1761
1762 if (lhs.first == rhs.first)
1763 {
1764 if (not coeff_is_zero(lhs.second - rhs.second))
1765 return false;
1766 it1.next_ne();
1767 it2.next_ne();
1768 continue;
1769 }
1770
1771 if (lhs.first < rhs.first)
1772 {
1773 if (not coeff_is_zero(lhs.second))
1774 return false;
1775 it1.next_ne();
1776 continue;
1777 }
1778
1779 if (not coeff_is_zero(rhs.second))
1780 return false;
1781 it2.next_ne();
1782 }
1783
1784 while (it1.has_curr())
1785 {
1786 if (not coeff_is_zero(it1.get_curr().second))
1787 return false;
1788 it1.next_ne();
1789 }
1790
1791 while (it2.has_curr())
1792 {
1793 if (not coeff_is_zero(it2.get_curr().second))
1794 return false;
1795 it2.next_ne();
1796 }
1797
1798 return true;
1799 }
1800
1802 [[nodiscard]] bool operator != (const Gen_Polynomial &q) const noexcept
1803 {
1804 return not (*this == q);
1805 }
1806
1807 // =================================================================
1808 // Iteration
1809 // =================================================================
1810
1815 template <class Op>
1816 void for_each_term(Op &&op) const
1817 {
1818 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1819 {
1820 auto &p = it.get_curr();
1821 op(p.first, p.second);
1822 }
1823 }
1824
1827 {
1829 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1830 {
1831 auto &p = it.get_curr();
1832 result.append({p.first, p.second});
1833 }
1834 return result;
1835 }
1836
1839 {
1840 return coeffs.keys();
1841 }
1842
1845 {
1846 return coeffs.values();
1847 }
1848
1849 // =================================================================
1850 // String representation
1851 // =================================================================
1852
1860 [[nodiscard]] std::string to_str(const std::string &var = "x") const
1861 {
1862 if (coeffs.is_empty())
1863 return "0";
1864
1865 std::ostringstream oss;
1866 bool first = true;
1867
1868 for_each_term_desc([&](size_t exp, const Coefficient &c)
1869 {
1870 bool negative = false;
1871 if constexpr (requires { c < Coefficient{}; })
1872 negative = c < Coefficient{};
1873
1874 Coefficient ac = negative ? -c : c;
1875
1876 // Sign
1877 if (first)
1878 {
1879 if (negative)
1880 oss << "-";
1881 }
1882 else
1883 oss << (negative ? " - " : " + ");
1884
1885 // Coefficient and variable
1886 if (exp == 0)
1887 oss << ac;
1888 else
1889 {
1890 if (not (ac == Coefficient(1)))
1891 oss << ac;
1892 oss << var;
1893 if (exp > 1)
1894 oss << "^" << exp;
1895 }
1896
1897 first = false;
1898 });
1899
1900 return oss.str();
1901 }
1902
1903 // =================================================================
1904 // Layer 5 — Exact Univariate Factorization
1905 // =================================================================
1906
1912 struct SfdTerm
1913 {
1916 };
1917
1923 [[nodiscard]] Coefficient get_coeff(size_t exp) const noexcept
1924 {
1925 return coeff_at(exp);
1926 }
1927
1933 void set_coeff(size_t exp, const Coefficient &c)
1934 {
1935 if (auto *slot = coeff_ptr(exp); slot != nullptr)
1936 {
1937 if (coeff_is_zero(c))
1938 coeffs.remove_key(exp);
1939 else
1940 *slot = c;
1941 }
1942 else if (not coeff_is_zero(c))
1943 coeffs.insert(exp, c);
1944 }
1945
1959 {
1960 if (coeffs.is_empty())
1961 return Coefficient{};
1962
1963 Coefficient result = Coefficient{};
1964 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1965 {
1966 auto &p = it.get_curr();
1967 result = std::gcd(result, p.second);
1968 }
1969
1970 // Ensure sign follows leading coefficient
1971 const Coefficient lc = leading_coeff();
1972 if (lc < Coefficient{} and result > Coefficient{})
1973 result = -result;
1974
1975 return result;
1976 }
1977
1988 {
1989 if (is_zero())
1990 return Gen_Polynomial();
1991
1992 Coefficient c = content();
1993 if (c == Coefficient{} or c == Coefficient(1) or c == Coefficient(-1))
1994 {
1995 // Already primitive; ensure leading coefficient is positive
1996 Gen_Polynomial result = *this;
1997 if (leading_coeff() < Coefficient{})
1998 result.scale_inplace(Coefficient(-1));
1999 return result;
2000 }
2001
2002 Gen_Polynomial result;
2003 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
2004 {
2005 auto &p = it.get_curr();
2006 result.coeffs.insert(p.first, p.second / c);
2007 }
2008
2009 // Ensure leading coefficient is positive
2010 if (result.leading_coeff() < Coefficient{})
2011 result.scale_inplace(Coefficient(-1));
2012
2013 return result;
2014 }
2015
2029 requires std::is_integral_v<Coefficient>
2030 {
2031 if (a.is_zero())
2032 return b.primitive_part();
2033 if (b.is_zero())
2034 return a.primitive_part();
2035
2036 a = a.primitive_part();
2037 b = b.primitive_part();
2038
2039 while (not b.is_zero())
2040 {
2041 auto [quo, rem] = a.pseudo_divmod(b);
2042 a = b;
2043 b = rem.is_zero() ? rem : rem.primitive_part();
2044 }
2045
2046 return a.primitive_part();
2047 }
2048
2066 {
2067 DynList<SfdTerm> result;
2068
2069 if (is_constant())
2070 return result;
2071
2072 const Gen_Polynomial f = primitive_part();
2073 const Gen_Polynomial fp = f.derivative();
2074
2075 Gen_Polynomial g = integer_gcd(f, fp);
2077
2078 size_t multiplicity = 1;
2079
2080 while (not h.is_constant())
2081 {
2083 h = integer_gcd(g, h);
2085
2086 if (not w.is_constant())
2087 result.append(SfdTerm{w.primitive_part(), multiplicity});
2088
2089 g = _integer_exact_quot(g, h);
2090 multiplicity++;
2091 }
2092
2093 // Final factor
2094 if (not g.is_constant())
2095 result.append(SfdTerm{g.primitive_part(), multiplicity});
2096
2097 return result;
2098 }
2099
2111 requires std::is_integral_v<Coefficient>
2112 {
2113 ah_domain_error_if(b.is_zero()) << "_integer_exact_quot: zero divisor";
2114 if (b.is_constant())
2115 {
2116 Coefficient d = b.leading_coeff();
2118 for (auto it = a.coeffs.get_it(); it.has_curr(); it.next_ne())
2119 {
2120 auto &p = it.get_curr();
2121 ah_domain_error_if(not coeff_is_zero(p.second % d))
2122 << "_integer_exact_quot: inexact constant division for coefficient " << p.second
2123 << " and divisor " << d;
2124 r.coeffs.insert(p.first, p.second / d);
2125 }
2126 return r;
2127 }
2128 auto [q, rem] = a.pseudo_divmod(b);
2129 ah_domain_error_if(not rem.is_zero())
2130 << "_integer_exact_quot: divisor does not divide dividend exactly";
2131 return q.is_zero() ? q : q.primitive_part();
2132 }
2133
2135 requires std::is_integral_v<Coefficient>
2136 {
2138 f.for_each_term([&reduced, mod](size_t exp, const Coefficient &coeff)
2139 {
2140 Coefficient c = coeff % mod;
2141 if (c < Coefficient{})
2142 c = c + mod;
2143 if (c != Coefficient{})
2144 reduced.set_coeff(exp, c);
2145 });
2146 return reduced;
2147 }
2148
2150 requires std::is_integral_v<Coefficient>
2151 {
2152 if (mod == 0 or mod == 1)
2153 return 0;
2154
2155 uint64_t result = 0;
2156 f.for_each_term([&result, x, mod](size_t exp, const Coefficient &coeff)
2157 {
2159 const uint64_t xpow = mod_exp(x, exp, mod);
2160 const uint64_t term = mod_mul(c, xpow, mod);
2161 result = (result + term) % mod;
2162 });
2163 return result;
2164 }
2165
2168 Coefficient mod)
2169 requires std::is_integral_v<Coefficient>
2170 {
2171 ah_domain_error_if(f.degree() == 0)
2172 << "divide_by_monic_linear_mod: constant polynomial is not divisible by x - r";
2173
2174 const size_t degree = f.degree();
2176
2177 synthetic(degree) = ((f.get_coeff(degree) % mod) + mod) % mod;
2178
2179 for (size_t idx = degree; idx-- > 0;)
2180 {
2181 Coefficient term = (f.get_coeff(idx) % mod + mod) % mod;
2182 Coefficient next = (term + (synthetic(idx + 1) * root) % mod) % mod;
2183 synthetic(idx) = next;
2184 }
2185
2187 << "divide_by_monic_linear_mod: x - r does not divide polynomial modulo p";
2188
2190 for (size_t exp = 0; exp < degree; ++exp)
2191 if (synthetic(exp + 1) != Coefficient{})
2193
2194 return quotient;
2195 }
2196
2198 requires std::is_integral_v<Coefficient>
2199 {
2200 DynList<Coefficient> result;
2201
2203 if (abs_value == 0)
2204 return result;
2205
2206 for (uint64_t d = 1; d * d <= abs_value; ++d)
2207 if (abs_value % d == 0)
2208 {
2209 result.append(static_cast<Coefficient>(d));
2210 const uint64_t other = abs_value / d;
2211 if (other != d)
2212 result.append(static_cast<Coefficient>(other));
2213 }
2214
2215 return result;
2216 }
2217
2228 requires std::is_integral_v<Coefficient>
2229 {
2230 try
2231 {
2232 auto [q, r] = f.divmod(candidate);
2233 if (not r.is_zero())
2234 return false;
2235 quotient = std::move(q);
2236 return true;
2237 }
2238 catch (const std::domain_error &)
2239 {
2240 return false;
2241 }
2242 }
2243
2245 Coefficient a,
2246 Coefficient b,
2248 requires std::is_integral_v<Coefficient>
2249 {
2250 if (f.is_zero() or f.degree() == 0)
2251 return false;
2252
2253 if (a == Coefficient{})
2254 return false;
2255
2256 const size_t degree = f.degree();
2258
2259 const Coefficient leading = f.get_coeff(degree);
2260 if (leading % a != Coefficient{})
2261 return false;
2262
2263 q_coeffs(degree - 1) = leading / a;
2264
2265 for (size_t k = degree - 1; k > 0; --k)
2266 {
2267 const Coefficient numerator = f.get_coeff(k) - b * q_coeffs(k);
2268 if (numerator % a != Coefficient{})
2269 return false;
2270 q_coeffs(k - 1) = numerator / a;
2271 }
2272
2273 if (f.get_coeff(0) - b * q_coeffs(0) != Coefficient{})
2274 return false;
2275
2277 for (size_t exp = 0; exp < degree; ++exp)
2278 if (q_coeffs(exp) != Coefficient{})
2279 quotient.set_coeff(exp, q_coeffs(exp));
2280
2281 return true;
2282 }
2283
2285 Gen_Polynomial &factor,
2287 requires std::is_integral_v<Coefficient>
2288 {
2289 if (f.is_zero() or f.degree() == 0)
2290 return false;
2291
2292 if (f.get_coeff(0) == Coefficient{})
2293 {
2295 {
2296 factor = Gen_Polynomial(Coefficient(1), 1);
2297 return true;
2298 }
2299 return false;
2300 }
2301
2304
2305 for (auto den_it = denominator_divs.get_it(); den_it.has_curr(); den_it.next_ne())
2306 {
2307 const Coefficient den = den_it.get_curr();
2308
2309 for (auto num_it = numerator_divs.get_it(); num_it.has_curr(); num_it.next_ne())
2310 {
2311 const Coefficient num_abs = num_it.get_curr();
2312
2313 for (Coefficient sign : {Coefficient(1), Coefficient(-1)})
2314 {
2315 const Coefficient num = sign * num_abs;
2316 if (std::gcd(polynomial_detail::abs_to_u64(num),
2318 continue;
2319
2321 {
2322 factor = Gen_Polynomial();
2323 factor.set_coeff(0, -num);
2324 factor.set_coeff(1, den);
2325 return true;
2326 }
2327 }
2328 }
2329 }
2330
2331 return false;
2332 }
2333
2343 Gen_Polynomial &factor,
2345 requires std::is_integral_v<Coefficient>
2346 {
2347 if (f.is_zero() or f.degree() < 2)
2348 return false;
2349
2350 if (f.get_coeff(0) == Coefficient{})
2351 return false;
2352
2355 if (leading_divs.is_empty() or constant_divs.is_empty())
2356 return false;
2357
2358 const double abs_lc = std::abs(static_cast<double>(f.leading_coeff()));
2359 double max_ratio = 0.0;
2360 for (auto it = f.coeffs.get_it(); it.has_curr(); it.next_ne())
2361 {
2362 const auto &term = it.get_curr();
2363 if (term.first == f.degree())
2364 continue;
2365 if (const double ratio = std::abs(static_cast<double>(term.second)) / abs_lc;
2366 ratio > max_ratio)
2367 max_ratio = ratio;
2368 }
2369
2370 const double root_bound = 1.0 + max_ratio;
2371
2372 for (auto a_it = leading_divs.get_it(); a_it.has_curr(); a_it.next_ne())
2373 {
2374 const Coefficient a = a_it.get_curr();
2375 const int64_t b_bound =
2376 static_cast<int64_t>(std::ceil(2.0 * std::abs(static_cast<double>(a)) * root_bound));
2377
2378 for (auto c_it = constant_divs.get_it(); c_it.has_curr(); c_it.next_ne())
2379 {
2380 const Coefficient c_abs = c_it.get_curr();
2381
2382 for (Coefficient sign : {Coefficient(1), Coefficient(-1)})
2383 {
2384 const Coefficient c = sign * c_abs;
2385
2386 for (int64_t b_i = -b_bound; b_i <= b_bound; ++b_i)
2387 {
2388 const Coefficient b = static_cast<Coefficient>(b_i);
2389 const uint64_t gcd_ab =
2391 if (std::gcd(gcd_ab, polynomial_detail::abs_to_u64(c)) != 1)
2392 continue;
2393
2395 candidate.set_coeff(0, c);
2396 if (b != Coefficient{})
2397 candidate.set_coeff(1, b);
2398 candidate.set_coeff(2, a);
2399
2402 {
2403 factor = candidate.primitive_part();
2404 quotient = q.is_zero() ? q : q.primitive_part();
2405 return true;
2406 }
2407 }
2408 }
2409 }
2410 }
2411
2412 return false;
2413 }
2414
2424 Gen_Polynomial &factor,
2426 requires std::is_integral_v<Coefficient>
2427 {
2428 if (f.is_zero() or f.degree() < 3)
2429 return false;
2430
2431 if (f.get_coeff(0) == Coefficient{})
2432 return false;
2433
2436 if (leading_divs.is_empty() or constant_divs.is_empty())
2437 return false;
2438
2439 const double abs_lc = std::abs(static_cast<double>(f.leading_coeff()));
2440 double l2_norm_sq = 0.0;
2441 for (auto it = f.coeffs.get_it(); it.has_curr(); it.next_ne())
2442 {
2443 const auto &term = it.get_curr();
2444 const double coeff_abs = std::abs(static_cast<double>(term.second));
2446 }
2447
2448 const int64_t coeff_bound = static_cast<int64_t>(std::ceil(8.0 * std::sqrt(l2_norm_sq) / abs_lc));
2449 if (coeff_bound <= 0)
2450 return false;
2451
2452 for (auto a_it = leading_divs.get_it(); a_it.has_curr(); a_it.next_ne())
2453 {
2454 const Coefficient a = a_it.get_curr();
2455 if (polynomial_detail::abs_to_u64(a) > static_cast<uint64_t>(coeff_bound))
2456 continue;
2457
2458 for (auto d_it = constant_divs.get_it(); d_it.has_curr(); d_it.next_ne())
2459 {
2460 const Coefficient d_abs = d_it.get_curr();
2462 continue;
2463
2464 for (Coefficient sign : {Coefficient(1), Coefficient(-1)})
2465 {
2466 const Coefficient d = sign * d_abs;
2467
2468 for (int64_t b_i = -coeff_bound; b_i <= coeff_bound; ++b_i)
2469 {
2470 const Coefficient b = static_cast<Coefficient>(b_i);
2471 const uint64_t gcd_ab =
2473
2474 for (int64_t c_i = -coeff_bound; c_i <= coeff_bound; ++c_i)
2475 {
2476 const Coefficient c = static_cast<Coefficient>(c_i);
2478 if (std::gcd(gcd_abc, polynomial_detail::abs_to_u64(d)) != 1)
2479 continue;
2480
2482 candidate.set_coeff(0, d);
2483 if (c != Coefficient{})
2484 candidate.set_coeff(1, c);
2485 if (b != Coefficient{})
2486 candidate.set_coeff(2, b);
2487 candidate.set_coeff(3, a);
2488
2491 {
2492 factor = candidate.primitive_part();
2493 quotient = q.is_zero() ? q : q.primitive_part();
2494 return true;
2495 }
2496 }
2497 }
2498 }
2499 }
2500 }
2501
2502 return false;
2503 }
2504
2523 requires std::is_integral_v<Coefficient>
2524 {
2525 ah_domain_error_if(p <= Coefficient(1)) << "factor_mod_p: modulus p must be > 1, got " << p;
2526
2528
2529 if (f.is_zero() or f.is_constant())
2530 return result;
2531
2532 Gen_Polynomial remaining = reduce_coeffs_mod(f, p);
2533
2534 for (Coefficient r = Coefficient{}; r < p; r = r + Coefficient(1))
2535 {
2536 while (not remaining.is_zero() and remaining.degree() > 0 and
2537 eval_mod_u64(remaining,
2540 {
2541 Gen_Polynomial factor;
2542 factor.set_coeff(0, ((-r) % p + p) % p);
2543 factor.set_coeff(1, Coefficient(1));
2544 result.append(factor);
2545 remaining = divide_by_monic_linear_mod(remaining, r, p);
2546 }
2547 }
2548
2549 if (not remaining.is_zero() and not remaining.is_constant())
2550 result.append(remaining);
2551
2552 return result;
2553 }
2554
2568 {
2569 if (is_zero())
2570 return 0.0;
2571
2572 const size_t d = degree();
2573 if (d == 0)
2574 return 0.0;
2575
2577 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
2578 {
2579 auto &p = it.get_curr();
2580 Coefficient abs_c = p.second < Coefficient{} ? -p.second : p.second;
2581 if (abs_c > max_coeff)
2582 max_coeff = abs_c;
2583 }
2584
2585 const auto d_real = static_cast<double>(d);
2586 const auto max_real = static_cast<double>(max_coeff);
2587 const double two_pow_d = polynomial_detail::power(2.0, d);
2588
2589 return std::sqrt(d_real + 1.0) * two_pow_d * max_real;
2590 }
2591
2615 Coefficient p,
2616 size_t levels)
2617 requires std::is_integral_v<Coefficient>
2618 {
2619 ah_domain_error_if(p <= Coefficient(1)) << "hensel_lift: modulus p must be > 1, got " << p;
2620 ah_domain_error_if(levels == 0) << "hensel_lift: levels must be >= 1, got " << levels;
2621 ah_domain_error_if(levels > 30)
2622 << "hensel_lift: levels must be <= 30 to avoid overflow, got " << levels;
2623
2624 if (factors.is_empty())
2625 return factors;
2626
2627 const size_t target_steps = static_cast<size_t>(1) << levels;
2629 const auto checked_mul_u64 = [](uint64_t lhs, uint64_t rhs, const char *name) -> uint64_t
2630 {
2631 ah_domain_error_if(rhs != 0 and lhs > std::numeric_limits<uint64_t>::max() / rhs)
2632 << "hensel_lift: overflow computing " << name << " (" << lhs << " * " << rhs << ")";
2633 return lhs * rhs;
2634 };
2635
2637 for (size_t i = 1; i < target_steps; ++i)
2638 target_u64 = checked_mul_u64(target_u64, p_u64, "target_u64");
2639
2640 ah_domain_error_if(target_u64 > static_cast<uint64_t>(std::numeric_limits<Coefficient>::max()))
2641 << "hensel_lift: target modulus " << target_u64 << " does not fit in coefficient type";
2642 const Coefficient p_power = static_cast<Coefficient>(target_u64);
2644
2646 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
2647 {
2648 const Gen_Polynomial &factor = it.get_curr();
2649 bool lifted_linear_factor = false;
2650
2651 if (factor.degree() == 1 and factor.get_coeff(1) == Coefficient(1))
2652 {
2655 bool simple = true;
2656
2657 for (size_t step = 1; step < target_steps; ++step)
2658 {
2659 const uint64_t next_mod = checked_mul_u64(modulus, p_u64, "next_mod");
2662
2663 if (f_val % modulus != 0 or std::gcd(deriv_modp, p_u64) != 1)
2664 {
2665 simple = false;
2666 break;
2667 }
2668
2669 const uint64_t e = (f_val / modulus) % p_u64;
2671 const uint64_t t = (p_u64 - mod_mul(e, inv, p_u64)) % p_u64;
2672
2673 const uint64_t increment = checked_mul_u64(modulus, t, "root increment");
2674#if defined(_MSC_VER) or defined(_WIN32)
2675 // MSVC has no __int128. clang-cl on Windows has __int128 but
2676 // lacks the __umodti3 runtime helper for 128-bit modulo.
2677 // Compute (root+increment) % next_mod without 128-bit arithmetic.
2678 {
2679 const uint64_t a = root % next_mod;
2680 const uint64_t b = increment % next_mod;
2681 root = (a >= next_mod - b) ? (a - (next_mod - b)) : (a + b);
2682 }
2683#else
2684 root = static_cast<uint64_t>((static_cast<unsigned __int128>(root) + increment) %
2685 next_mod);
2686#endif
2687 modulus = next_mod;
2688 }
2689
2690 if (simple)
2691 {
2693 lifted.set_coeff(0,
2694 polynomial_detail::centered_from_mod_u64<Coefficient>(
2696 lifted.set_coeff(1, Coefficient(1));
2697 result.append(lifted);
2698 lifted_linear_factor = true;
2699 }
2700 }
2701
2703 continue;
2704
2706 factor.for_each_term([&normalized, p_power](size_t exp, const Coefficient &coeff)
2707 {
2709 if (canonical < Coefficient{})
2710 canonical += p_power;
2711 if (canonical != Coefficient{})
2712 normalized.set_coeff(exp, canonical);
2713 });
2714 result.append(normalized);
2715 }
2716
2717 return result;
2718 }
2719
2752 {
2753 DynList<SfdTerm> result;
2754
2755 if (is_zero() or is_constant())
2756 return result;
2757
2758 const Coefficient c = content();
2759 if (c != Coefficient(1))
2760 result.append(SfdTerm{Gen_Polynomial(c), 1});
2761
2762 // Step 1: Get primitive part
2764
2765 // Step 2: Compute square-free decomposition
2766 DynList<SfdTerm> sfd_terms = prim.yun_sfd();
2767
2768 if (sfd_terms.is_empty())
2769 return result;
2770
2771 // Step 3: For each square-free group, attempt factorization
2772 for (auto it = sfd_terms.get_it(); it.has_curr(); it.next_ne())
2773 {
2774 auto &sfd_term = it.get_curr();
2775 Gen_Polynomial factor = sfd_term.factor;
2776 size_t mult = sfd_term.multiplicity;
2777 Gen_Polynomial remaining = factor.primitive_part();
2778
2779 while (remaining.degree() > 0)
2780 {
2783 break;
2784
2785 result.append(SfdTerm{exact_linear.primitive_part(), mult});
2786 remaining = quotient.is_zero() ? quotient : quotient.primitive_part();
2787 }
2788
2789 while (remaining.degree() > 1)
2790 {
2793 break;
2794
2795 result.append(SfdTerm{exact_quadratic.primitive_part(), mult});
2796 remaining = quotient.is_zero() ? quotient : quotient.primitive_part();
2797 }
2798
2799 while (remaining.degree() > 2)
2800 {
2803 break;
2804
2805 result.append(SfdTerm{exact_cubic.primitive_part(), mult});
2806 remaining = quotient.is_zero() ? quotient : quotient.primitive_part();
2807 }
2808
2809 if (remaining.is_constant() or remaining.is_zero())
2810 continue;
2811
2812 // Try modular factorization with a few small primes and exact redivision.
2813 for (Coefficient p : {Coefficient(3), Coefficient(5), Coefficient(7), Coefficient(11)})
2814 {
2815 if (remaining.leading_coeff() % p == Coefficient{})
2816 continue;
2817
2818 auto mod_factors = factor_mod_p(remaining, p);
2819 if (mod_factors.size() <= 1)
2820 continue;
2821
2822 auto lifted = hensel_lift(remaining, mod_factors, p, 2);
2823
2824 bool changed = false;
2825 for (auto jt = lifted.get_it(); jt.has_curr(); jt.next_ne())
2826 {
2827 Gen_Polynomial cand = jt.get_curr().primitive_part();
2828 if (cand.is_zero() or cand.is_constant() or cand.degree() != 1)
2829 continue;
2830
2832 if (not try_divide_by_linear_factor(remaining, cand.get_coeff(1), cand.get_coeff(0),
2833 quotient))
2834 continue;
2835
2836 result.append(SfdTerm{cand, mult});
2837 remaining = quotient.is_zero() ? quotient : quotient.primitive_part();
2838 changed = true;
2839
2840 if (remaining.is_constant())
2841 break;
2842 }
2843
2844 if (changed)
2845 {
2846 if (remaining.is_constant())
2847 break;
2848 }
2849 }
2850
2851 if (not remaining.is_constant() and not remaining.is_zero())
2852 result.append(SfdTerm{remaining.primitive_part(), mult});
2853 }
2854
2855 return result;
2856 }
2857};
2858
2859// =================================================================
2860// Free functions
2861// =================================================================
2862
2864template <typename C>
2866{
2867 return p * s;
2868}
2869
2871template <typename C>
2873{
2874 return p + s;
2875}
2876
2878template <typename C>
2880{
2881 return Gen_Polynomial<C>(s) - p;
2882}
2883
2885template <typename C>
2886std::ostream &operator << (std::ostream &out, const Gen_Polynomial<C> &p)
2887{
2888 return out << p.to_str();
2889}
2890
2891// =================================================================
2892// Convenience type aliases
2893// =================================================================
2894
2897
2900
2903
2906
2907} // end namespace Aleph
2908
2909#endif // TPL_POLYNOMIAL_H
Exception handling system with formatted messages for Aleph-w.
#define ah_domain_error_if(C)
Throws std::domain_error if condition holds.
Definition ah-errors.H:527
long double h
Definition btreepic.C:154
long double w
Definition btreepic.C:153
size_t size_t int32_t value
Definition ca-c-api.h:116
size_t size_t int32_t * out
Definition ca-c-api.h:120
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
void putn(const size_t n)
Reserve n additional logical slots in the array without value-initializing them.
Definition tpl_array.H:310
Doubly-linked list (defined in tpl_dynList.H).
Definition htlist.H:1155
T & append(const T &item)
Definition htlist.H:1271
Generic key-value map implemented on top of a binary search tree.
Univariate polynomial over a generic coefficient ring.
Gen_Polynomial nth_derivative(size_t n) const
-th derivative .
std::pair< size_t, Coefficient > Term
static Gen_Polynomial divide_by_monic_linear_mod(const Gen_Polynomial &f, Coefficient root, Coefficient mod)
Gen_Polynomial(const DynList< std::pair< size_t, Coefficient > > &term_list)
Sparse construction from a list of (exponent, coefficient) pairs.
DynList< Gen_Polynomial > sturm_chain() const
Sturm sequence of this polynomial.
bool is_monic() const noexcept
True if leading coefficient equals 1.
Coefficient horner_eval(const Coefficient &x) const
Evaluate using gap-aware Horner's method.
Gen_Polynomial negate_arg() const
Argument negation: .
static Gen_Polynomial zero()
The zero polynomial.
void for_each_term(Op &&op) const
Iterate over non-zero terms in ascending exponent order.
static Gen_Polynomial one()
The constant polynomial 1.
Coefficient content() const
Content: GCD of all coefficients.
void divide_scalar_inplace(const Coefficient &s)
Divide every coefficient in place by a scalar.
size_t num_terms() const noexcept
Number of non-zero terms.
void add_scaled_shifted(const Gen_Polynomial &src, const Coefficient &scale, size_t shift=0)
Add scale times src shifted by shift to this polynomial.
DynList< size_t > exponents() const
All exponents with non-zero coefficients (sorted ascending).
void add_to_coeff(size_t exp, const Coefficient &delta)
Add a delta to one coefficient, erasing the term if it cancels.
Coefficient * coeff_ptr(size_t exp) noexcept
Pointer to stored coefficient, or null if absent.
Gen_Polynomial operator/(const Coefficient &s) const
Divide by a scalar.
static DynList< Gen_Polynomial > factor_mod_p(const Gen_Polynomial &f, Coefficient p)
Factorization over integers mod p (trial method).
Gen_Polynomial primitive_part() const
Primitive part: polynomial divided by its content.
Coefficient cauchy_bound() const
Cauchy upper bound on absolute value of roots.
Gen_Polynomial & operator-=(const Gen_Polynomial &q)
In-place subtraction.
static bool try_extract_rational_linear_factor(const Gen_Polynomial &f, Gen_Polynomial &factor, Gen_Polynomial &quotient)
void remove_zeros()
Remove all entries whose coefficient is (approximately) zero.
Coefficient eval(const Coefficient &x) const
Evaluate with adaptive strategy.
Coefficient coeff_at(size_t exp) const
Read coefficient at exponent (0 if absent).
Gen_Polynomial & operator+=(const Gen_Polynomial &q)
In-place addition.
Gen_Polynomial truncate(size_t n) const
Truncate to degree less than n.
std::string to_str(const std::string &var="x") const
Human-readable string representation.
static Gen_Polynomial from_roots(const DynList< Coefficient > &roots)
Build polynomial from its roots: .
static Gen_Polynomial interpolate(const DynList< std::pair< Coefficient, Coefficient > > &points)
Polynomial interpolation through a set of points.
Gen_Polynomial operator*(const Gen_Polynomial &q) const
Polynomial multiplication.
bool is_constant() const noexcept
True if constant or zero.
size_t count_real_roots(const Coefficient &a, const Coefficient &b) const
Count distinct real roots in via Sturm's theorem.
void scale_inplace(const Coefficient &s)
Multiply every coefficient in place by a scalar.
Gen_Polynomial pow(size_t n) const
Exponentiation by repeated squaring.
Coefficient operator[](size_t exp) const
Coefficient at exponent exp (0 if absent).
DynList< SfdTerm > yun_sfd() const
Yun's square-free decomposition (SFD).
size_t sign_variations() const
Count sign changes in the coefficient sequence.
bool is_monomial() const noexcept
True if exactly one non-zero term.
bool has_term(size_t exp) const noexcept
True if there is a non-zero coefficient at exp.
const Coefficient * coeff_ptr(size_t exp) const noexcept
Pointer to stored coefficient, or null if absent.
static Xgcd_Result xgcd(Gen_Polynomial a, Gen_Polynomial b)
static Gen_Polynomial integer_gcd(Gen_Polynomial a, Gen_Polynomial b)
Polynomial GCD over integers (Euclidean algorithm with primitive parts).
double mignotte_bound() const
Mignotte bound on integer roots.
Gen_Polynomial compose(const Gen_Polynomial &q) const
Composition .
Coefficient sparse_eval(const Coefficient &x) const
Evaluate using sparse evaluation.
static constexpr Coefficient epsilon() noexcept
Tolerance threshold for floating-point zero detection.
Coefficient definite_integral(const Coefficient &a, const Coefficient &b) const
Definite integral .
bool is_zero() const noexcept
True if this is the zero polynomial.
static bool try_exact_candidate_division(const Gen_Polynomial &f, const Gen_Polynomial &candidate, Gen_Polynomial &quotient)
Try exact division but treat inexact integral division as a rejected candidate.
Gen_Polynomial operator+(const Gen_Polynomial &q) const
Polynomial addition.
size_t count_all_real_roots() const
Total number of distinct real roots.
static size_t sturm_sign_changes(const DynList< Gen_Polynomial > &chain, const Coefficient &x)
Count sign changes in a Sturm chain at a given point.
static Gen_Polynomial _integer_exact_quot(const Gen_Polynomial &a, const Gen_Polynomial &b)
Integer-safe exact quotient using pseudo-division.
Gen_Polynomial(const DynList< Coefficient > &l)
Dense construction from DynList.
Coefficient bisect_root(Coefficient a, Coefficient b, Coefficient tol=Coefficient(1e-12), size_t max_iter=200) const
Find a root by bisection in .
void for_each_term_desc(Op &&op) const
Iterate over non-zero terms in descending exponent order.
Gen_Polynomial(const Coefficient &c)
Constant polynomial .
size_t degree() const noexcept
Degree of the polynomial.
DynList< SfdTerm > factorize() const
Main factorization over integers.
Gen_Polynomial shift_down(size_t k) const
Divide by (shift exponents down).
static DynList< Gen_Polynomial > hensel_lift(const Gen_Polynomial &f, DynList< Gen_Polynomial > factors, Coefficient p, size_t levels)
Hensel lifting of modular factors.
DynList< std::pair< size_t, Coefficient > > terms_list() const
All non-zero terms as a sorted list of (exponent, coefficient).
std::pair< Gen_Polynomial, Gen_Polynomial > divmod(const Gen_Polynomial &d) const
Polynomial long division: returns (quotient, remainder).
static bool try_extract_primitive_quadratic_factor(const Gen_Polynomial &f, Gen_Polynomial &factor, Gen_Polynomial &quotient)
Try to extract an exact primitive quadratic factor over .
Gen_Polynomial & operator/=(const Coefficient &s)
In-place scalar division.
static Gen_Polynomial lcm(const Gen_Polynomial &a, const Gen_Polynomial &b)
Least common multiple.
static uint64_t eval_mod_u64(const Gen_Polynomial &f, uint64_t x, uint64_t mod)
Array< Coefficient > to_dense() const
Dense coefficient vector.
Gen_Polynomial shift_up(size_t k) const
Multiply by (shift exponents up).
DynMapTree< size_t, Coefficient > coeffs
Gen_Polynomial shift(const Coefficient &k) const
Taylor shift: .
static Gen_Polynomial gcd(Gen_Polynomial a, Gen_Polynomial b)
Greatest common divisor (Euclidean algorithm).
Gen_Polynomial square_free() const
Square-free part: .
Gen_Polynomial derivative() const
Formal derivative .
DynList< Coefficient > multi_eval(const DynList< Coefficient > &points) const
Evaluate at multiple points.
std::pair< Gen_Polynomial, Gen_Polynomial > pseudo_divmod(const Gen_Polynomial &d) const
Pseudo-division for non-field coefficient types.
Gen_Polynomial & operator*=(const Gen_Polynomial &q)
In-place multiplication.
Gen_Polynomial integral(const Coefficient &C=Coefficient{}) const
Formal indefinite integral with constant of integration.
Gen_Polynomial operator-() const
Unary negation.
static bool coeff_is_zero(const Coefficient &c) noexcept
Test whether a coefficient is (approximately) zero.
static Gen_Polynomial x_to(size_t n)
The monomial .
static bool try_divide_by_linear_factor(const Gen_Polynomial &f, Coefficient a, Coefficient b, Gen_Polynomial &quotient)
bool operator!=(const Gen_Polynomial &q) const noexcept
Inequality comparison.
Gen_Polynomial()=default
Default constructor: the zero polynomial.
Gen_Polynomial multiply_by_monic_linear(const Coefficient &c) const
Specialized multiplication by a monic linear factor .
static bool try_extract_primitive_cubic_factor(const Gen_Polynomial &f, Gen_Polynomial &factor, Gen_Polynomial &quotient)
Try to extract an exact primitive cubic factor over .
bool operator==(const Gen_Polynomial &q) const noexcept
Equality comparison.
Coefficient leading_coeff() const noexcept
Leading coefficient (of the highest-degree term).
Gen_Polynomial to_monic() const
Make monic: divide by leading coefficient.
Gen_Polynomial & operator%=(const Gen_Polynomial &d)
In-place polynomial remainder.
Coefficient newton_root(Coefficient x0, Coefficient tol=Coefficient(1e-12), size_t max_iter=100) const
Find a root by Newton-Raphson iteration.
Gen_Polynomial(std::initializer_list< Coefficient > il)
Dense construction from initializer list.
Gen_Polynomial operator%(const Gen_Polynomial &d) const
Polynomial remainder.
Coefficient operator()(const Coefficient &x) const
Syntactic sugar: p(x) calls eval.
static Gen_Polynomial reduce_coeffs_mod(const Gen_Polynomial &f, Coefficient mod)
void set_coeff(size_t exp, const Coefficient &c)
Set coefficient at exponent; removes entry if zero.
Gen_Polynomial reverse() const
Reciprocal polynomial: .
DynList< Coefficient > coefficients() const
All non-zero coefficients in ascending exponent order.
Gen_Polynomial(const Coefficient &c, size_t exp)
Monomial .
static DynList< Coefficient > positive_divisors(Coefficient value)
Coefficient get_coeff(size_t exp) const noexcept
Coefficient accessor (read-only at exponent).
void for_each(Operation &operation)
Traverse all the container and performs an operation on each element.
Definition ah-dry.H:796
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_exp_function > > exp(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4077
__gmp_expr< T, __gmp_binary_expr< __gmp_expr< T, U >, unsigned long int, __gmp_root_function > > root(const __gmp_expr< T, U > &expr, unsigned long int l)
Definition gmpfrxx.h:4071
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
Singly linked list implementations with head-tail access.
Safe modular arithmetic, extended Euclidean algorithm, and Chinese Remainder Theorem.
C power(C base, size_t exp)
Fast exponentiation by squaring.
uint64_t normalize_mod_u64(Int value, const uint64_t modulus) noexcept
uint64_t abs_to_u64(Int value) noexcept
Int centered_from_mod_u64(const uint64_t value, const uint64_t modulus) noexcept
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
Gen_MultiPolynomial< C, M > operator+(const C &s, const Gen_MultiPolynomial< C, M > &p)
Scalar + polynomial (commutative).
uint64_t mod_inv(const uint64_t a, const uint64_t m)
Modular Inverse.
and
Check uniqueness with explicit hash + equality functors.
Gen_MultiPolynomial< C, M > operator-(const C &s, const Gen_MultiPolynomial< C, M > &p)
Scalar - polynomial.
Matrix< Trow, Tcol, NumType > operator*(const NumType &scalar, const Matrix< Trow, Tcol, NumType > &m)
Scalar-matrix multiplication (scalar * matrix).
Definition al-matrix.H:995
uint64_t mod_exp(uint64_t base, uint64_t exp, const uint64_t m)
Modular exponentiation.
uint64_t mod_mul(uint64_t a, uint64_t b, uint64_t m)
Safe 64-bit modular multiplication.
void next()
Advance all underlying iterators (bounds-checked).
Definition ah-zip.H:171
std::ostream & operator<<(std::ostream &osObject, const Field< T > &rightOp)
Definition ahField.H:121
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
STL namespace.
Square-free decomposition term: factor and multiplicity.
Extended GCD: computes g, s, t such that .
Gen_Polynomial t
Bezout coefficient for the second argument.
Gen_Polynomial s
Bezout coefficient for the first argument.
Gen_Polynomial g
Greatest common divisor (monic).
Aleph::DynList< T > keys() const
Definition ah-dry.H:1863
static int * k
gsl_rng * r
Dynamic array container with automatic resizing.
Dynamic key-value map based on balanced binary search trees.
DynList< int > l