Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
tpl_multi_polynomial.H
Go to the documentation of this file.
1/*
2 This file is part of Aleph-w library
3
4 Copyright (c) 2002-2026 Leandro Rabindranath Leon
5
6 Permission is hereby granted, free of charge, to any person obtaining a copy
7 of this software and associated documentation files (the "Software"), to deal
8 in the Software without restriction, including without limitation the rights
9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 copies of the Software, and to permit persons to whom the Software is
11 furnished to do so, subject to the following conditions:
12
13 The above copyright notice and this permission notice shall be included in all
14 copies or substantial portions of the Software.
15
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 SOFTWARE.
23*/
24
41#ifndef TPL_MULTI_POLYNOMIAL_H
42#define TPL_MULTI_POLYNOMIAL_H
43
44#include <sstream>
45#include <string>
46#include <type_traits>
47#include <limits>
48#include <cmath>
49#include <utility>
50#include <iomanip>
51#include <future>
52#include <thread>
53#include <fstream>
54#include <numeric>
55#include <ah-errors.H>
56#include <ahSort.H>
57#include <tpl_array.H>
58#include <tpl_dynBinHeap.H>
59#include <tpl_dynMapTree.H>
60#include <htlist.H>
61#include <ah-parallel.H>
62#include <tpl_polynomial.H>
63
64namespace Aleph {
65
66// ===================================================================
67// Multi-index helpers
68// ===================================================================
69
71namespace multi_poly_detail {
72
74inline size_t total_degree(const Array<size_t> &idx) noexcept
75{
76 size_t sum = 0;
77 for (size_t i = 0; i < idx.size(); ++i)
78 sum += idx(i);
79 return sum;
80}
81
83inline bool is_zero_index(const Array<size_t> &idx) noexcept
84{
85 for (size_t i = 0; i < idx.size(); ++i)
86 if (idx(i) != 0)
87 return false;
88 return true;
89}
90
95{
96 const size_t n = std::max(a.size(), b.size());
97 Array<size_t> r(n, size_t{0});
98 for (size_t i = 0; i < a.size(); ++i)
99 r(i) += a(i);
100 for (size_t i = 0; i < b.size(); ++i)
101 r(i) += b(i);
102 return r;
103}
104
106inline Array<size_t> extend_index(const Array<size_t> &idx, size_t target)
107{
108 if (idx.size() >= target)
109 return idx;
110 Array<size_t> r(target, size_t{0});
111 for (size_t i = 0; i < idx.size(); ++i)
112 r(i) = idx(i);
113 return r;
114}
115
117template <typename T>
118T int_power(const T &base, size_t exp)
119{
120 T result = T(1);
121 T b = base;
122 while (exp > 0)
123 {
124 if (exp & 1)
125 result *= b;
126 b *= b;
127 exp >>= 1;
128 }
129 return result;
130}
131
132// ===================================================================
133// Phase 2 — Differential calculus, interpolation, serialization, parallelism
134// ===================================================================
135
137inline Array<size_t> decrement_index_at(const Array<size_t> &idx, const size_t var, const size_t n = 1)
138{
139 Array<size_t> r = idx;
140 r(var) -= n;
141 return r;
142}
143
147template <typename T>
148T falling_factorial(const size_t k, const size_t n)
149{
150 if (n == 0)
151 return T(1);
152 if (k < n)
153 return T(0);
154 T res = T(1);
155 for (size_t i = 0; i < n; ++i)
156 res *= T(k - i);
157 return res;
158}
159
164{
165 const size_t n = sizes.size();
166 Array<size_t> r(n, size_t{0});
167 for (size_t i = n; i > 0; --i)
168 {
169 r(i - 1) = flat % sizes(i - 1);
170 flat /= sizes(i - 1);
171 }
172 return r;
173}
174
176inline size_t multi_to_flat_index(const Array<size_t> &midx, const Array<size_t> &sizes)
177{
178 size_t flat = 0, stride = 1;
179 const size_t n = sizes.size();
180 for (size_t i = n; i > 0; --i)
181 {
182 flat += midx(i - 1) * stride;
183 stride *= sizes(i - 1);
184 }
185 return flat;
186}
187
189template <typename Coefficient>
191{
193 for (size_t j = 0; j < alpha.size(); ++j)
194 if (alpha(j) != 0)
195 v *= int_power(pt(j), alpha(j));
196 return v;
197}
198
200inline bool divides_index(const Array<size_t> &alpha, const Array<size_t> &beta, size_t nvars) noexcept
201{
202 for (size_t i = 0; i < nvars; ++i)
203 {
204 const size_t ai = i < alpha.size() ? alpha(i) : 0;
205 const size_t bi = i < beta.size() ? beta(i) : 0;
206 if (ai > bi)
207 return false;
208 }
209 return true;
210}
211
213inline Array<size_t> lcm_indices(const Array<size_t> &a, const Array<size_t> &b, const size_t nvars)
214{
215 Array<size_t> r(nvars, size_t{0});
216 for (size_t i = 0; i < nvars; ++i)
217 r(i) = std::max(i < a.size() ? a(i) : 0, i < b.size() ? b(i) : 0);
218 return r;
219}
220
223 const Array<size_t> &alpha,
224 const size_t nvars)
225{
226 Array<size_t> r(nvars, size_t{0});
227 for (size_t i = 0; i < nvars; ++i)
228 r(i) = (i < beta.size() ? beta(i) : 0) - (i < alpha.size() ? alpha(i) : 0);
229 return r;
230}
231
232} // namespace multi_poly_detail
233
234// ===================================================================
235// Monomial orderings
236// ===================================================================
237
245{
246 bool operator()(const Array<size_t> &a, const Array<size_t> &b) const noexcept
247 {
248 const size_t n = std::max(a.size(), b.size());
249 for (size_t i = 0; i < n; ++i)
250 {
251 const size_t ai = i < a.size() ? a(i) : 0;
252 const size_t bi = i < b.size() ? b(i) : 0;
253 if (ai < bi)
254 return true;
255 if (ai > bi)
256 return false;
257 }
258 return false;
259 }
260};
261
267{
268 bool operator()(const Array<size_t> &a, const Array<size_t> &b) const noexcept
269 {
270 const size_t da = multi_poly_detail::total_degree(a);
271 const size_t db = multi_poly_detail::total_degree(b);
272 if (da != db)
273 return da < db;
274 return Lex_Order()(a, b);
275 }
276};
277
285{
286 bool operator()(const Array<size_t> &a, const Array<size_t> &b) const noexcept
287 {
288 const size_t da = multi_poly_detail::total_degree(a);
289 const size_t db = multi_poly_detail::total_degree(b);
290 if (da != db)
291 return da < db;
292 const size_t n = std::max(a.size(), b.size());
293 for (size_t i = n; i > 0; --i)
294 {
295 const size_t ai = i - 1 < a.size() ? a(i - 1) : 0;
296 const size_t bi = i - 1 < b.size() ? b(i - 1) : 0;
297 if (ai > bi)
298 return true; // larger last exp → smaller in grevlex
299 if (ai < bi)
300 return false;
301 }
302 return false;
303 }
304};
305
306// ===================================================================
307// Gen_MultiPolynomial
308// ===================================================================
309
321template <typename Coefficient = double, class MonomOrder = Grevlex_Order>
323{
324 size_t nvars_ = 0;
325
327
330 {
332 }
333
335 requires(not std::is_integral_v<Coefficient>)
336 {
337 if (p.is_zero())
338 return;
339
340 Coefficient lc = p.leading_coeff();
342 p /= lc;
343 }
344
346 const Gen_MultiPolynomial &p)
347 {
348 for (size_t i = 0; i < basis.size(); ++i)
349 if (basis(i) == p)
350 return true;
351 return false;
352 }
353
355 const Array<size_t> &b,
356 const size_t nvars) noexcept
357 {
358 for (size_t v = 0; v < nvars; ++v)
359 {
360 const size_t av = v < a.size() ? a(v) : 0;
361 const size_t bv = v < b.size() ? b(v) : 0;
362 if (std::min(av, bv) != 0)
363 return false;
364 }
365 return true;
366 }
367
368 using Pair_Key = std::pair<size_t, size_t>;
370
371 [[nodiscard]] static Pair_Key canonical_pair(size_t i, size_t j) noexcept
372 {
373 return i < j ? std::make_pair(i, j) : std::make_pair(j, i);
374 }
375
377 {
378 size_t i = 0;
379 size_t j = 0;
381 size_t lcm_degree = 0;
382 };
383
385 {
386 if (lhs.lcm_degree != rhs.lcm_degree)
387 return lhs.lcm_degree < rhs.lcm_degree;
388
389 MonomOrder order;
390 if (order(lhs.lcm, rhs.lcm))
391 return true;
392 if (order(rhs.lcm, lhs.lcm))
393 return false;
394
395 return canonical_pair(lhs.i, lhs.j) < canonical_pair(rhs.i, rhs.j);
396 }
397
399 {
401 {
402 return lcm_pair_less(lhs, rhs);
403 }
404 };
405
407
408 [[nodiscard]] static bool pair_is_recorded(const Pair_Registry &pairs, size_t i, size_t j)
409 {
410 return pairs.contains(canonical_pair(i, j));
411 }
412
413 [[nodiscard]] static bool pair_is_zero_known(const Pair_Registry &zero_pairs, size_t i, size_t j)
414 {
415 return pair_is_recorded(zero_pairs, i, j);
416 }
417
419 size_t i, size_t j, const Array<Array<size_t>> &leading_monomials, size_t nvars)
420 {
421 const auto [a, b] = canonical_pair(i, j);
423 candidate.i = a;
424 candidate.j = b;
427 return candidate;
428 }
429
433 const size_t nvars)
434 {
435 for (size_t k = 0; k < leading_monomials.size(); ++k)
436 {
437 if (k == candidate.i or k == candidate.j)
438 continue;
439
441 continue;
442
445 return true;
446 }
447
448 return false;
449 }
450
452 {
453 ah_domain_error_if(queue.is_empty()) << "pop_best_pair: empty pair queue";
454
455 Pair_Candidate best = queue.getMin();
456 queued_pairs.remove_key(canonical_pair(best.i, best.j));
457 return best;
458 }
459
465 const size_t nvars)
466 {
468 return;
469
471 {
472 zero_pairs.insert(canonical_pair(candidate.i, candidate.j), true);
473 return;
474 }
475
477 {
478 zero_pairs.insert(canonical_pair(candidate.i, candidate.j), true);
479 return;
480 }
481
482 queue.insert(candidate);
484 }
485
488 requires(not std::is_integral_v<Coefficient>)
489 {
491
492 for (size_t i = 0; i < generators.size(); ++i)
493 {
496
497 if (reduced.is_empty())
498 {
499 reduced.append(std::move(g));
500 continue;
501 }
502
504 if (r.is_zero())
505 continue;
506
509 reduced.append(std::move(r));
510 }
511
512 return reduced;
513 }
514
516 requires(not std::is_integral_v<Coefficient>)
517 {
518 bool changed = true;
519 while (changed)
520 {
521 changed = false;
522 size_t idx_to_remove = basis.size();
523
524 for (size_t i = 0; i < basis.size(); ++i)
525 {
526 if (idx_to_remove < basis.size())
527 break;
528
529 Array<size_t> lm_i = basis(i).leading_monomial();
530
531 for (size_t j = 0; j < basis.size(); ++j)
532 {
533 if (i == j)
534 continue;
535
538 {
539 idx_to_remove = i;
540 break;
541 }
542 }
543 }
544
545 if (idx_to_remove < basis.size())
546 {
548 for (size_t i = 0; i < basis.size(); ++i)
549 if (i != idx_to_remove)
550 compact.append(std::move(basis(i)));
551 basis = std::move(compact);
552 changed = true;
553 }
554 }
555
556 return basis;
557 }
558
561 {
563 for (size_t i = 0; i < basis.size(); ++i)
564 {
565 if (basis(i).is_zero())
566 continue;
568 continue;
569 compact.append(basis(i));
570 }
571 return compact;
572 }
573
576 requires(not std::is_integral_v<Coefficient>)
577 {
578 bool changed = true;
579 while (changed)
580 {
581 changed = false;
582 for (size_t i = 0; i < basis.size(); ++i)
583 {
584 if (basis(i).is_zero())
585 {
587 for (size_t j = 0; j < basis.size(); ++j)
588 if (j != i)
589 compact.append(basis(j));
590 basis = std::move(compact);
591 changed = true;
592 break;
593 }
594
596 for (size_t j = 0; j < basis.size(); ++j)
597 if (j != i and not basis(j).is_zero())
598 others.append(basis(j));
599
600 if (others.is_empty())
601 {
603 continue;
604 }
605
607
608 if (r.is_zero())
609 {
611 for (size_t j = 0; j < basis.size(); ++j)
612 if (j != i)
613 compact.append(basis(j));
614 basis = std::move(compact);
615 changed = true;
616 break;
617 }
618
621 {
623 for (size_t j = 0; j < basis.size(); ++j)
624 if (j != i)
625 compact.append(basis(j));
626 basis = std::move(compact);
627 changed = true;
628 break;
629 }
630
631 if (r != basis(i))
632 {
633 basis(i) = std::move(r);
634 changed = true;
635 break;
636 }
637 }
638 }
639
641 for (size_t i = 0; i < basis.size(); ++i)
643 return basis;
644 }
645
648 Pair_Queue &queue,
651 requires(not std::is_integral_v<Coefficient>)
652 {
654 for (size_t i = 0; i < basis.size(); ++i)
655 leading_monomials.append(basis(i).leading_monomial());
656
657 queue = Pair_Queue();
660
661 if (basis.size() < 2)
662 return;
663
664 for (size_t i = 0; i < basis.size(); ++i)
665 for (size_t j = i + 1; j < basis.size(); ++j)
671 basis(0).nvars_);
672 }
673
674public:
675 // =================================================================
676 // Type aliases
677 // =================================================================
678
682
683 // =================================================================
684 // Coefficient zero-testing
685 // =================================================================
686
690 static constexpr Coefficient epsilon() noexcept
691 {
692 if constexpr (std::is_floating_point_v<Coefficient>)
693 return Coefficient(128) * std::numeric_limits<Coefficient>::epsilon();
694 else
695 return Coefficient{};
696 }
697
702 static bool coeff_is_zero(const Coefficient &c) noexcept
703 {
704 if constexpr (std::is_floating_point_v<Coefficient>)
705 return std::abs(c) <= epsilon();
706 else
707 return c == Coefficient{};
708 }
709
710 // =================================================================
711 // Constructors
712 // =================================================================
713
716
721 explicit Gen_MultiPolynomial(const size_t nvars, const Coefficient &c = Coefficient{})
722 : nvars_(nvars)
723 {
724 if (not coeff_is_zero(c))
725 coeffs.insert(Array<size_t>(nvars_, size_t{0}), c);
726 }
727
733 Gen_MultiPolynomial(const size_t nvars, const Array<size_t> &idx, const Coefficient &c)
734 : nvars_(nvars)
735 {
736 if (not coeff_is_zero(c))
737 coeffs.insert(norm(idx), c);
738 }
739
744 Gen_MultiPolynomial(const size_t nvars, const DynList<std::pair<Array<size_t>, Coefficient>> &ts)
745 : nvars_(nvars)
746 {
747 ts.for_each([this](const std::pair<Array<size_t>, Coefficient> &t)
748 {
749 add_to_coeff(t.first, t.second);
750 });
751 }
752
758 std::initializer_list<std::pair<Array<size_t>, Coefficient>> ts)
759 : nvars_(nvars)
760 {
761 for (const auto &t : ts)
762 add_to_coeff(t.first, t.second);
763 }
764
765 // =================================================================
766 // Factory helpers
767 // =================================================================
768
774 [[nodiscard]] static Gen_MultiPolynomial variable(size_t nvars, const size_t var)
775 {
776 ah_domain_error_if(var >= nvars) << "variable index " << var << " >= nvars " << nvars;
777 Array<size_t> idx(nvars, size_t{0});
778 idx(var) = 1;
779 return Gen_MultiPolynomial(nvars, idx, Coefficient(1));
780 }
781
789 const Array<size_t> &idx,
790 const Coefficient &c = Coefficient(1))
791 {
792 return Gen_MultiPolynomial(nvars, idx, c);
793 }
794
795 // =================================================================
796 // Properties
797 // =================================================================
798
801 {
802 return coeffs.is_empty();
803 }
804
807 {
808 if (coeffs.is_empty())
809 return true;
810 if (coeffs.size() != 1)
811 return false;
812 return multi_poly_detail::is_zero_index(coeffs.min().first);
813 }
814
817 {
818 return nvars_;
819 }
820
823 {
824 return coeffs.size();
825 }
826
831 {
832 size_t d = 0;
833 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
834 if (size_t td = multi_poly_detail::total_degree(it.get_curr().first); td > d)
835 d = td;
836 return d;
837 }
838
843 [[nodiscard]] size_t degree_in(size_t var) const noexcept
844 {
845 size_t d = 0;
846 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
847 {
848 const auto &idx = it.get_curr().first;
849 if (const size_t e = var < idx.size() ? idx(var) : 0; e > d)
850 d = e;
851 }
852 return d;
853 }
854
855 // =================================================================
856 // Leading-term access
857 // =================================================================
858
863 [[nodiscard]] std::pair<Array<size_t>, Coefficient> leading_term() const
864 {
865 ah_domain_error_if(is_zero()) << "leading_term of zero polynomial";
866 const auto &m = coeffs.max();
867 return {m.first, m.second};
868 }
869
875 {
876 return leading_term().second;
877 }
878
884 {
885 return leading_term().first;
886 }
887
888 // =================================================================
889 // Coefficient access
890 // =================================================================
891
897 {
898 auto *p = coeffs.search(norm(idx));
899 return p ? p->second : Coefficient{};
900 }
901
902 // =================================================================
903 // Iteration
904 // =================================================================
905
909 template <class Op>
910 void for_each_term(Op &&op) const
911 {
912 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
913 {
914 const auto &p = it.get_curr();
915 op(p.first, p.second);
916 }
917 }
918
922 template <class Op>
923 void for_each_term_desc(Op &&op) const
924 {
926 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
927 {
928 const auto &p = it.get_curr();
929 tmp.insert({p.first, p.second}); // prepend → reversed
930 }
931 tmp.for_each([&op](const std::pair<Array<size_t>, Coefficient> &t)
932 {
933 op(t.first, t.second);
934 });
935 }
936
941 {
943 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
944 {
945 r.append({idx, c});
946 });
947 return r;
948 }
949
950 // =================================================================
951 // Coefficient modification
952 // =================================================================
953
961 void add_to_coeff(const Array<size_t> &idx, const Coefficient &delta)
962 {
963 if (coeff_is_zero(delta))
964 return;
965
966 auto nidx = norm(idx);
967 auto *p = coeffs.search(nidx);
968 if (p != nullptr)
969 {
970 p->second = p->second + delta;
971 if (coeff_is_zero(p->second))
972 coeffs.remove_key(nidx);
973 }
974 else
975 coeffs.insert(nidx, delta);
976 }
977
980 {
982 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
983 {
984 auto &p = it.get_curr();
985 if (coeff_is_zero(p.second))
986 zs.append(p.first);
987 }
988 zs.for_each([this](const Array<size_t> &k)
989 {
990 coeffs.remove_key(k);
991 });
992 }
993
998 {
999 if (coeffs.is_empty())
1000 return;
1001
1002 if (coeff_is_zero(s))
1003 {
1004 coeffs = decltype(coeffs)();
1005 return;
1006 }
1007
1008 if (s == Coefficient(1))
1009 return;
1010
1011 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1012 it.get_curr().second *= s;
1013
1014 remove_zeros();
1015 }
1016
1022 {
1023 ah_domain_error_if(coeff_is_zero(s)) << "multivariate polynomial division by zero scalar";
1024
1025 if (s == Coefficient(1))
1026 return;
1027
1028 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1029 it.get_curr().second /= s;
1030
1031 remove_zeros();
1032 }
1033
1034 // =================================================================
1035 // Arithmetic — polynomial ± polynomial
1036 // =================================================================
1037
1043 {
1044 Gen_MultiPolynomial r = *this;
1045 r += q;
1046 return r;
1047 }
1048
1054 {
1055 if (this == &q)
1056 {
1058 return *this;
1059 }
1060
1061 nvars_ = std::max(nvars_, q.nvars_);
1062 q.for_each_term([this](const Array<size_t> &idx, const Coefficient &c)
1063 {
1064 add_to_coeff(idx, c);
1065 });
1066 return *this;
1067 }
1068
1074 {
1075 Gen_MultiPolynomial r = *this;
1076 r += s;
1077 return r;
1078 }
1079
1085 {
1086 add_to_coeff(Array<size_t>(nvars_, size_t{0}), s);
1087 return *this;
1088 }
1089
1095 {
1096 Gen_MultiPolynomial r = *this;
1097 r -= q;
1098 return r;
1099 }
1100
1106 {
1107 if (this == &q)
1108 {
1109 coeffs = decltype(coeffs)();
1110 return *this;
1111 }
1112
1113 nvars_ = std::max(nvars_, q.nvars_);
1114 q.for_each_term([this](const Array<size_t> &idx, const Coefficient &c)
1115 {
1116 add_to_coeff(idx, -c);
1117 });
1118 return *this;
1119 }
1120
1126 {
1127 Gen_MultiPolynomial r = *this;
1128 r -= s;
1129 return r;
1130 }
1131
1137 {
1138 add_to_coeff(Array<size_t>(nvars_, size_t{0}), -s);
1139 return *this;
1140 }
1141
1146 {
1148 r.nvars_ = nvars_;
1149 for_each_term([&r](const Array<size_t> &idx, const Coefficient &c)
1150 {
1151 r.coeffs.insert(idx, -c);
1152 });
1153 return r;
1154 }
1155
1156 // =================================================================
1157 // Arithmetic — polynomial * polynomial
1158 // =================================================================
1159
1168 {
1169 const size_t nv = std::max(nvars_, q.nvars_);
1170
1171 if (is_zero() or q.is_zero())
1172 return Gen_MultiPolynomial(nv);
1173
1174 const Gen_MultiPolynomial *outer = this;
1175 const Gen_MultiPolynomial *inner = &q;
1176 if (q.num_terms() < num_terms())
1177 {
1178 outer = &q;
1179 inner = this;
1180 }
1181
1183 r.nvars_ = nv;
1184
1185 outer->for_each_term([&](const Array<size_t> &ai, const Coefficient &ac)
1186 {
1187 inner->for_each_term([&](const Array<size_t> &bi, const Coefficient &bc)
1188 {
1191 r.add_to_coeff(si, ac * bc);
1192 });
1193 });
1194
1195 return r;
1196 }
1197
1203 {
1204 *this = *this * q;
1205 return *this;
1206 }
1207
1208 // =================================================================
1209 // Arithmetic — scalar operations
1210 // =================================================================
1211
1217 {
1218 if (coeff_is_zero(s))
1220
1222 r.nvars_ = nvars_;
1223 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
1224 {
1225 if (Coefficient prod = c * s; not coeff_is_zero(prod))
1226 r.coeffs.insert(idx, prod);
1227 });
1228 return r;
1229 }
1230
1236 {
1237 scale_inplace(s);
1238 return *this;
1239 }
1240
1247 {
1248 ah_domain_error_if(coeff_is_zero(s)) << "multivariate polynomial division by zero scalar";
1249
1251 r.nvars_ = nvars_;
1252 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
1253 {
1254 Coefficient q = c / s;
1255 if (not coeff_is_zero(q))
1256 r.coeffs.insert(idx, q);
1257 });
1258 return r;
1259 }
1260
1267 {
1269 return *this;
1270 }
1271
1272 // =================================================================
1273 // Exponentiation
1274 // =================================================================
1275
1281 {
1283 Gen_MultiPolynomial base = *this;
1284 while (n > 0)
1285 {
1286 if (n & 1)
1287 result *= base;
1288 base *= base;
1289 n >>= 1;
1290 }
1291 return result;
1292 }
1293
1294 // =================================================================
1295 // Evaluation
1296 // =================================================================
1297
1304 {
1306 << "eval: point has " << pt.size() << " components but polynomial has " << nvars_
1307 << " variables";
1308
1309 Coefficient result = Coefficient{};
1310
1311 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
1312 {
1313 Coefficient term = c;
1314 for (size_t j = 0; j < idx.size(); ++j)
1315 {
1316 if (idx(j) == 0)
1317 continue;
1318 term *= multi_poly_detail::int_power(pt(j), idx(j));
1319 }
1320 result += term;
1321 });
1322
1323 return result;
1324 }
1325
1332 {
1333 return eval(pt);
1334 }
1335
1336 // =================================================================
1337 // Comparison
1338 // =================================================================
1339
1345 {
1346 if (num_terms() != q.num_terms())
1347 return false;
1348
1349 bool eq = true;
1350 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
1351 {
1352 if (not eq)
1353 return;
1354 if (not coeff_is_zero(c - q.coeff_at(idx)))
1355 eq = false;
1356 });
1357 return eq;
1358 }
1359
1362 {
1363 return not(*this == q);
1364 }
1365
1366 // =================================================================
1367 // String representation
1368 // =================================================================
1369
1379 {
1380 if (is_zero())
1381 return "0";
1382
1383 // Build name lookup
1385 {
1386 size_t j = 0;
1387 names.for_each([&](const std::string &n)
1388 {
1389 if (j < nvars_)
1390 vn.append(n);
1391 ++j;
1392 });
1393 }
1394 while (vn.size() < nvars_)
1395 if (nvars_ <= 3)
1396 {
1397 constexpr char abc[] = {'x', 'y', 'z'};
1398 vn.append(std::string(1, abc[vn.size()]));
1399 }
1400 else
1401 vn.append("x" + std::to_string(vn.size()));
1402
1403 std::ostringstream oss;
1404 bool first = true;
1405
1406 for_each_term_desc([&](const Array<size_t> &idx, const Coefficient &c)
1407 {
1408 Coefficient ac = c;
1409 bool neg = false;
1410 if constexpr (std::is_floating_point_v<Coefficient>)
1411 {
1412 if (c < 0)
1413 {
1414 ac = -c;
1415 neg = true;
1416 }
1417 }
1418 else
1419 {
1420 if (c < Coefficient{})
1421 {
1422 ac = -c;
1423 neg = true;
1424 }
1425 }
1426
1428
1429 // Sign
1430 if (first)
1431 {
1432 if (neg)
1433 oss << "-";
1434 }
1435 else
1436 oss << (neg ? " - " : " + ");
1437
1438 // Coefficient (suppress 1 / -1 when variables are present)
1439 bool cp = false;
1441 {
1442 oss << ac;
1443 cp = true;
1444 }
1445
1446 // Variables
1447 if (has_vars)
1448 {
1449 bool fv = true;
1450 for (size_t j = 0; j < idx.size(); ++j)
1451 {
1452 if (idx(j) == 0)
1453 continue;
1454 if (cp or not fv)
1455 oss << "*";
1456 oss << vn(j);
1457 if (idx(j) > 1)
1458 oss << "^" << idx(j);
1459 fv = false;
1460 }
1461 }
1462
1463 first = false;
1464 });
1465
1466 return oss.str();
1467 }
1468
1469 // =================================================================
1470 // Promote
1471 // =================================================================
1472
1482 {
1484 << "cannot demote from " << nvars_ << " to " << new_nv << " variables";
1485
1486 if (new_nv == nvars_)
1487 return *this;
1488
1490 r.nvars_ = new_nv;
1491 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
1492 {
1493 r.coeffs.insert(multi_poly_detail::extend_index(idx, new_nv), c);
1494 });
1495 return r;
1496 }
1497
1498 // =================================================================
1499 // Least-Squares Fitting (Layer 2 - Industrial)
1500 // =================================================================
1501
1517 const Array<std::pair<Array<Coefficient>, Coefficient>> &data,
1518 const size_t nvars,
1519 const Array<Array<size_t>> &basis)
1520 {
1521 const size_t m = data.size();
1522 const size_t b = basis.size();
1523
1524 ah_domain_error_if(m == 0) << "fit: data is empty";
1525 ah_domain_error_if(b == 0) << "fit: basis is empty";
1526
1527 // Build design matrix A [m x b]
1530
1531 for (size_t i = 0; i < m; ++i)
1532 {
1533 for (size_t j = 0; j < b; ++j)
1534 A(i)(j) = _eval_monomial(data(i).first, basis(j));
1535 y(i) = data(i).second;
1536 }
1537
1538 // Normal equations: (A^T A) c = A^T y
1541
1542 for (size_t i = 0; i < b; ++i)
1543 {
1544 for (size_t j = 0; j < b; ++j)
1545 {
1546 AtA(i)(j) = Coefficient{};
1547 for (size_t k = 0; k < m; ++k)
1548 AtA(i)(j) += A(k)(i) * A(k)(j);
1549 }
1550
1551 Aty(i) = Coefficient{};
1552 for (size_t k = 0; k < m; ++k)
1553 Aty(i) += A(k)(i) * y(k);
1554 }
1555
1556 // Solve via Gaussian elimination with pivoting
1557 Array<Coefficient> c(b);
1558 for (size_t col = 0; col < b; ++col)
1559 {
1560 // Partial pivoting
1561 size_t prow = col;
1562 Coefficient pval = std::abs(AtA(col)(col));
1563 for (size_t row = col + 1; row < b; ++row)
1564 {
1565 Coefficient av = std::abs(AtA(row)(col));
1566 if (av > pval)
1567 {
1568 pval = av;
1569 prow = row;
1570 }
1571 }
1572
1573 ah_domain_error_if(coeff_is_zero(AtA(prow)(col))) << "fit: singular system at column " << col;
1574
1575 if (prow != col)
1576 {
1577 std::swap(AtA(col), AtA(prow));
1578 std::swap(Aty(col), Aty(prow));
1579 }
1580
1581 // Eliminate
1582 for (size_t row = col + 1; row < b; ++row)
1583 {
1584 Coefficient f = AtA(row)(col) / AtA(col)(col);
1585 for (size_t j = col; j < b; ++j)
1586 AtA(row)(j) -= f * AtA(col)(j);
1587 Aty(row) -= f * Aty(col);
1588 }
1589 }
1590
1591 // Back-substitution
1592 for (size_t i = b; i > 0; --i)
1593 {
1594 c(i - 1) = Aty(i - 1);
1595 for (size_t j = i; j < b; ++j)
1596 c(i - 1) -= AtA(i - 1)(j) * c(j);
1597 c(i - 1) /= AtA(i - 1)(i - 1);
1598 }
1599
1600 // Assemble polynomial
1601 Gen_MultiPolynomial result(nvars);
1602 for (size_t j = 0; j < b; ++j)
1603 {
1604 if (not coeff_is_zero(c(j)))
1605 result.add_to_coeff(basis(j), c(j));
1606 }
1607
1608 return result;
1609 }
1610
1629 const Array<std::pair<Array<Coefficient>, Coefficient>> &data,
1630 size_t nvars,
1631 const Array<Array<size_t>> &basis,
1633 {
1634 const size_t m = data.size();
1635 const size_t b = basis.size();
1636
1637 ah_domain_error_if(m == 0) << "fit_weighted: data is empty";
1638 ah_domain_error_if(b == 0) << "fit_weighted: basis is empty";
1639 ah_domain_error_if(weights.size() != m)
1640 << "fit_weighted: weights.size() " << weights.size() << " != data.size() " << m;
1641
1642 // Build weighted design matrix: A_ij = sqrt(w_i) * basis_j(x_i)
1645
1646 for (size_t i = 0; i < m; ++i)
1647 {
1648 const Coefficient sqrt_w = std::sqrt(weights(i));
1649 for (size_t j = 0; j < b; ++j)
1650 A(i)(j) = sqrt_w * _eval_monomial(data(i).first, basis(j));
1651 y_w(i) = sqrt_w * data(i).second;
1652 }
1653
1654 // Normal equations: (A^T A) c = A^T y
1657
1658 for (size_t i = 0; i < b; ++i)
1659 {
1660 for (size_t j = 0; j < b; ++j)
1661 {
1662 AtA(i)(j) = Coefficient{};
1663 for (size_t k = 0; k < m; ++k)
1664 AtA(i)(j) += A(k)(i) * A(k)(j);
1665 }
1666
1667 Aty(i) = Coefficient{};
1668 for (size_t k = 0; k < m; ++k)
1669 Aty(i) += A(k)(i) * y_w(k);
1670 }
1671
1672 // Solve via Gaussian elimination
1673 Array<Coefficient> c(b);
1674 for (size_t col = 0; col < b; ++col)
1675 {
1676 // Partial pivoting
1677 size_t prow = col;
1678 Coefficient pval = std::abs(AtA(col)(col));
1679 for (size_t row = col + 1; row < b; ++row)
1680 {
1681 Coefficient av = std::abs(AtA(row)(col));
1682 if (av > pval)
1683 {
1684 pval = av;
1685 prow = row;
1686 }
1687 }
1688
1690 << "fit_weighted: singular system at column " << col;
1691
1692 if (prow != col)
1693 {
1694 std::swap(AtA(col), AtA(prow));
1695 std::swap(Aty(col), Aty(prow));
1696 }
1697
1698 // Eliminate
1699 for (size_t row = col + 1; row < b; ++row)
1700 {
1701 Coefficient f = AtA(row)(col) / AtA(col)(col);
1702 for (size_t j = col; j < b; ++j)
1703 AtA(row)(j) -= f * AtA(col)(j);
1704 Aty(row) -= f * Aty(col);
1705 }
1706 }
1707
1708 // Back-substitution
1709 for (size_t i = b; i > 0; --i)
1710 {
1711 c(i - 1) = Aty(i - 1);
1712 for (size_t j = i; j < b; ++j)
1713 c(i - 1) -= AtA(i - 1)(j) * c(j);
1714 c(i - 1) /= AtA(i - 1)(i - 1);
1715 }
1716
1717 // Assemble polynomial
1718 Gen_MultiPolynomial result(nvars);
1719 for (size_t j = 0; j < b; ++j)
1720 if (not coeff_is_zero(c(j)))
1721 result.add_to_coeff(basis(j), c(j));
1722
1723 return result;
1724 }
1725
1750 const Array<std::pair<Array<Coefficient>, Coefficient>> &data,
1751 size_t nvars,
1752 const Array<Array<size_t>> &basis,
1753 Coefficient *lambda_used = nullptr,
1754 double *gcv_score = nullptr)
1755 {
1756 const size_t m = data.size();
1757 const size_t b = basis.size();
1758
1759 ah_domain_error_if(m == 0) << "fit_ridge: data is empty";
1760 ah_domain_error_if(b == 0) << "fit_ridge: basis is empty";
1761
1762 // Build design matrix A [m x b]
1765
1766 for (size_t i = 0; i < m; ++i)
1767 {
1768 for (size_t j = 0; j < b; ++j)
1769 A(i)(j) = _eval_monomial(data(i).first, basis(j));
1770 y(i) = data(i).second;
1771 }
1772
1773 // Compute A^T A and A^T y
1776
1777 for (size_t i = 0; i < b; ++i)
1778 {
1779 for (size_t j = 0; j < b; ++j)
1780 {
1781 AtA(i)(j) = Coefficient{};
1782 for (size_t k = 0; k < m; ++k)
1783 AtA(i)(j) += A(k)(i) * A(k)(j);
1784 }
1785
1786 Aty(i) = Coefficient{};
1787 for (size_t k = 0; k < m; ++k)
1788 Aty(i) += A(k)(i) * y(k);
1789 }
1790
1791 // GCV-based lambda selection: test a range
1793 double best_gcv = std::numeric_limits<double>::max();
1794 constexpr int nlambda = 50;
1795
1796 for (int trial = 0; trial < nlambda; ++trial)
1797 {
1798 // Logarithmically spaced lambdas: 1e-6 to 1e6
1799 const double log_lambda = -6.0 + 12.0 * trial / (nlambda - 1.0);
1800 Coefficient lambda_try = Coefficient(std::pow(10.0, log_lambda));
1801
1802 // Solve (A^T A + lambda I) c = A^T y via Gaussian elimination
1805
1806 // Add lambda to diagonal
1807 for (size_t i = 0; i < b; ++i)
1808 sys(i)(i) += lambda_try;
1809
1810 // Gaussian elimination with pivoting
1811 Array<Coefficient> c(b);
1812 bool singular = false;
1813
1814 for (size_t col = 0; col < b; ++col)
1815 {
1816 // Partial pivoting
1817 size_t prow = col;
1818 Coefficient pval = std::abs(sys(col)(col));
1819 for (size_t row = col + 1; row < b; ++row)
1820 if (Coefficient av = std::abs(sys(row)(col)); av > pval)
1821 {
1822 pval = av;
1823 prow = row;
1824 }
1825
1826 if (coeff_is_zero(sys(prow)(col)))
1827 {
1828 singular = true;
1829 break;
1830 }
1831
1832 if (prow != col)
1833 {
1834 std::swap(sys(col), sys(prow));
1835 std::swap(rhs(col), rhs(prow));
1836 }
1837
1838 // Eliminate
1839 for (size_t row = col + 1; row < b; ++row)
1840 {
1841 Coefficient f = sys(row)(col) / sys(col)(col);
1842 for (size_t j = col; j < b; ++j)
1843 sys(row)(j) -= f * sys(col)(j);
1844 rhs(row) -= f * rhs(col);
1845 }
1846 }
1847
1848 if (singular)
1849 continue;
1850
1851 // Back-substitution
1852 for (size_t i = b; i > 0; --i)
1853 {
1854 c(i - 1) = rhs(i - 1);
1855 for (size_t j = i; j < b; ++j)
1856 c(i - 1) -= sys(i - 1)(j) * c(j);
1857 c(i - 1) /= sys(i - 1)(i - 1);
1858 }
1859
1860 // Compute GCV score: sum((y - Ac)_i^2) / (1 - trace(A(A^T A + lambda I)^{-1} A^T) / m)^2
1862 for (size_t i = 0; i < m; ++i)
1863 {
1865 for (size_t j = 0; j < b; ++j)
1866 pred += A(i)(j) * c(j);
1867 Coefficient res = y(i) - pred;
1869 }
1870
1871 // Effective DOF approximation: trace(A(A^T A + lambda I)^{-1} A^T)
1872 // For simplicity, use b as upper bound for trace
1873 double denominator = 1.0 - static_cast<double>(b) / static_cast<double>(m);
1874 if (denominator < 0.01)
1875 denominator = 0.01;
1876
1877 double gcv_val = static_cast<double>(sum_sq_residuals) / (denominator * denominator);
1878
1879 if (gcv_val < best_gcv)
1880 {
1881 best_gcv = gcv_val;
1883 }
1884 }
1885
1886 // Solve final system with best_lambda
1889 for (size_t i = 0; i < b; ++i)
1890 final_sys(i)(i) += best_lambda;
1891
1893 for (size_t col = 0; col < b; ++col)
1894 {
1895 size_t prow = col;
1896 Coefficient pval = std::abs(final_sys(col)(col));
1897 for (size_t row = col + 1; row < b; ++row)
1898 if (Coefficient av = std::abs(final_sys(row)(col)); av > pval)
1899 {
1900 pval = av;
1901 prow = row;
1902 }
1903
1904 if (prow != col)
1905 {
1906 std::swap(final_sys(col), final_sys(prow));
1907 std::swap(final_rhs(col), final_rhs(prow));
1908 }
1909
1910 for (size_t row = col + 1; row < b; ++row)
1911 {
1912 Coefficient f = final_sys(row)(col) / final_sys(col)(col);
1913 for (size_t j = col; j < b; ++j)
1914 final_sys(row)(j) -= f * final_sys(col)(j);
1915 final_rhs(row) -= f * final_rhs(col);
1916 }
1917 }
1918
1919 for (size_t i = b; i > 0; --i)
1920 {
1921 c_final(i - 1) = final_rhs(i - 1);
1922 for (size_t j = i; j < b; ++j)
1923 c_final(i - 1) -= final_sys(i - 1)(j) * c_final(j);
1924 c_final(i - 1) /= final_sys(i - 1)(i - 1);
1925 }
1926
1927 // Output statistics if requested
1928 if (lambda_used != nullptr)
1930 if (gcv_score != nullptr)
1932
1933 // Assemble polynomial
1934 Gen_MultiPolynomial result(nvars);
1935 for (size_t j = 0; j < b; ++j)
1936 if (not coeff_is_zero(c_final(j)))
1937 result.add_to_coeff(basis(j), c_final(j));
1938
1939 return result;
1940 }
1941
1942 // =================================================================
1943 // Phase 2 — Differential Calculus
1944 // =================================================================
1945
1962 [[nodiscard]] Gen_MultiPolynomial partial(size_t var, size_t n = 1) const
1963 {
1964 if (n == 0)
1965 return *this;
1966 ah_domain_error_if(var >= nvars_) << "variable " << var << " >= nvars " << nvars_;
1967
1969 for_each_term([&](const Array<size_t> &alpha, const Coefficient &c)
1970 {
1971 const size_t k = alpha(var);
1972 if (k < n)
1973 return; // term vanishes
1974
1975 // Falling factorial: k * (k-1) * ... * (k-n+1)
1976 const Coefficient ff = multi_poly_detail::falling_factorial<Coefficient>(k, n);
1977 if (result.coeff_is_zero(ff))
1978 return;
1979
1980 const Coefficient new_c = c * ff;
1982 result.add_to_coeff(new_idx, new_c);
1983 });
1984 return result;
1985 }
1986
1994 {
1996 for (size_t k = 0; k < nvars_; ++k)
1997 result(k) = partial(k);
1998 return result;
1999 }
2000
2010 {
2011 const size_t n = nvars_;
2014 for (size_t i = 0; i < n; ++i)
2015 {
2016 H(i)(i) = partial(i, 2);
2017 for (size_t j = i + 1; j < n; ++j)
2018 {
2019 H(i)(j) = partial(i).partial(j);
2020 H(j)(i) = H(i)(j); // Symmetry
2021 }
2022 }
2023 return H;
2024 }
2025
2036 {
2038 << "eval_gradient: point size " << pt.size() << " < nvars " << nvars_;
2039
2040 const auto g = gradient();
2042 for (size_t k = 0; k < nvars_; ++k)
2043 result(k) = g(k).eval(pt);
2044 return result;
2045 }
2046
2057 {
2059 << "eval_hessian: point size " << pt.size() << " < nvars " << nvars_;
2060
2061 const auto H = hessian();
2062 const size_t n = nvars_;
2064 for (size_t i = 0; i < n; ++i)
2065 for (size_t j = 0; j < n; ++j)
2066 result(i)(j) = H(i)(j).eval(pt);
2067 return result;
2068 }
2069
2070 // =================================================================
2071 // Phase 2 — Interpolation (Newton Divided Differences)
2072 // =================================================================
2073
2091 const Array<Coefficient> &values,
2092 size_t nvars)
2093 {
2094 // Validate grid
2095 ah_domain_error_if(grid.size() != nvars)
2096 << "interpolate: grid size " << grid.size() << " != nvars " << nvars;
2097
2098 // Compute sizes and total points
2099 Array<size_t> sizes(nvars, size_t{0});
2100 size_t total = 1;
2101 for (size_t d = 0; d < nvars; ++d)
2102 {
2103 sizes(d) = grid(d).size();
2104 ah_domain_error_if(sizes(d) == 0) << "interpolate: grid[" << d << "] is empty";
2105 total *= sizes(d);
2106 }
2107
2108 ah_domain_error_if(values.size() != total)
2109 << "interpolate: values size " << values.size() << " != total " << total;
2110
2111 // Check for duplicate nodes in each dimension
2112 for (size_t d = 0; d < nvars; ++d)
2113 {
2114 const Array<Coefficient> &nodes = grid(d);
2115 for (size_t i = 0; i < nodes.size(); ++i)
2116 for (size_t j = i + 1; j < nodes.size(); ++j)
2118 << "interpolate: duplicate node at dimension " << d;
2119 }
2120
2121 // Initialize table: polynomials for each point (flat indexing)
2123 for (size_t f = 0; f < total; ++f)
2124 table(f) = Gen_MultiPolynomial(nvars, values(f));
2125
2126 // Process each dimension
2127 for (size_t dim = 0; dim < nvars; ++dim)
2128 {
2129 const size_t m = sizes(dim);
2130 const Array<Coefficient> &nodes = grid(dim);
2131
2132 // Compute stride for iterating in this dimension
2133 size_t stride = 1;
2134 for (size_t d = dim + 1; d < nvars; ++d)
2135 stride *= sizes(d);
2136
2137 // Number of slices
2138 const size_t num_slices = total / m;
2139
2140 // Process each slice
2141 for (size_t slice = 0; slice < num_slices; ++slice)
2142 {
2143 // Compute the base flat index for this slice
2144 size_t slice_flat = 0;
2145 size_t tmp = slice;
2146 size_t mult = 1;
2147 for (size_t d = nvars; d > 0; --d)
2148 {
2149 if (d - 1 == dim)
2150 continue;
2151 size_t sz = sizes(d - 1);
2152 slice_flat += (tmp % sz) * mult;
2153 tmp /= sz;
2154 mult *= sz;
2155 }
2156
2157 // Collect divided-differences table for this slice
2159 for (size_t i = 0; i < m; ++i)
2160 dd(i) = table(slice_flat + i * stride);
2161
2162 // Compute divided differences in-place
2163 for (size_t order = 1; order < m; ++order)
2164 for (size_t i = m; i > order; --i)
2165 {
2166 const Coefficient denom = nodes(i - 1) - nodes(i - 1 - order);
2167 dd(i - 1) = (dd(i - 1) - dd(i - 2)) / denom;
2168 }
2169
2170 // Reconstruct Newton polynomial in this dimension
2171 const auto xd = Gen_MultiPolynomial::variable(nvars, dim);
2172 Gen_MultiPolynomial result = dd(0);
2174 for (size_t i = 1; i < m; ++i)
2175 {
2176 basis = basis * (xd - Gen_MultiPolynomial(nvars, nodes(i - 1)));
2177 result = result + dd(i) * basis;
2178 }
2179
2180 // Write back to table
2181 table(slice_flat) = result;
2182 }
2183 }
2184
2185 return table(0);
2186 }
2187
2188 // =================================================================
2189 // Phase 2 — Serialization
2190 // =================================================================
2191
2200 [[nodiscard]] std::string to_json() const
2201 {
2202 std::ostringstream oss;
2203 oss << R"({"nvars":)" << nvars_ << R"(,"terms":[)";
2204 bool first = true;
2205 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
2206 {
2207 if (not first)
2208 oss << ",";
2209 oss << R"({"e":[)";
2210 for (size_t j = 0; j < idx.size(); ++j)
2211 {
2212 if (j > 0)
2213 oss << ",";
2214 oss << idx(j);
2215 }
2216 oss << R"(],"c":)" << std::setprecision(17) << c << "}";
2217 first = false;
2218 });
2219 oss << R"(]})";
2220 return oss.str();
2221 }
2222
2236 [[nodiscard]] static Gen_MultiPolynomial from_json(const std::string &s)
2237 {
2238 // Simple parser for our JSON format
2239 size_t nvars = 0;
2240 Gen_MultiPolynomial result;
2241
2242 // Find "nvars":n
2243 size_t pos = s.find("\"nvars\":");
2244 ah_domain_error_if(pos == std::string::npos) << "from_json: missing nvars";
2245 pos += 8;
2246 size_t pos_comma = s.find(',', pos);
2247 nvars = std::stoul(s.substr(pos, pos_comma - pos));
2248 result.nvars_ = nvars;
2249
2250 // Parse terms
2251 pos = s.find("\"terms\":[");
2252 ah_domain_error_if(pos == std::string::npos) << "from_json: missing terms";
2253 pos += 9;
2254
2255 while (pos < s.size() && s[pos] != ']')
2256 {
2257 // Find "e":[...]
2258 size_t e_pos = s.find("\"e\":[", pos);
2259 if (e_pos == std::string::npos)
2260 break;
2261 e_pos += 5;
2262 size_t e_end = s.find(']', e_pos);
2263 std::string e_str = s.substr(e_pos, e_end - e_pos);
2264
2265 // Parse exponents
2266 Array<size_t> idx(nvars, size_t{0});
2267 size_t idx_i = 0;
2268 std::istringstream iss_e(e_str);
2269 std::string token;
2270 while (std::getline(iss_e, token, ',') and idx_i < nvars)
2271 idx(idx_i++) = std::stoul(token);
2272
2273 // Find "c":value
2274 size_t c_pos = s.find("\"c\":", e_end);
2275 ah_domain_error_if(c_pos == std::string::npos) << "from_json: missing c";
2276 c_pos += 4;
2277 const size_t c_end = s.find('}', c_pos);
2278 Coefficient c_val = Coefficient(std::stod(s.substr(c_pos, c_end - c_pos)));
2279
2280 result.add_to_coeff(idx, c_val);
2281
2282 // Move to next term
2283 pos = s.find("},", e_end);
2284 if (pos == std::string::npos)
2285 break;
2286 pos += 2;
2287 }
2288
2289 return result;
2290 }
2291
2300 void to_binary(std::ostream &out) const
2301 {
2302 const uint64_t nv = nvars_;
2303 out.write(reinterpret_cast<const char *>(&nv), sizeof(nv));
2304
2305 const uint64_t nt = num_terms();
2306 out.write(reinterpret_cast<const char *>(&nt), sizeof(nt));
2307
2308 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
2309 {
2310 for (size_t j = 0; j < nvars_; ++j)
2311 {
2312 uint64_t exp = idx(j);
2313 out.write(reinterpret_cast<const char *>(&exp), sizeof(exp));
2314 }
2315 const auto c_double = static_cast<double>(c);
2316 out.write(reinterpret_cast<const char *>(&c_double), sizeof(c_double));
2317 });
2318 }
2319
2330 {
2331 uint64_t nv = 0;
2332 in.read(reinterpret_cast<char *>(&nv), sizeof(nv));
2333 size_t nvars = nv;
2334
2335 uint64_t nt = 0;
2336 in.read(reinterpret_cast<char *>(&nt), sizeof(nt));
2337 size_t n_terms = nt;
2338
2339 Gen_MultiPolynomial result(nvars);
2340 for (size_t i = 0; i < n_terms; ++i)
2341 {
2342 Array<size_t> idx(nvars, size_t{0});
2343 for (size_t j = 0; j < nvars; ++j)
2344 {
2345 uint64_t exp = 0;
2346 in.read(reinterpret_cast<char *>(&exp), sizeof(exp));
2347 idx(j) = exp;
2348 }
2349 double c_double = 0.0;
2350 in.read(reinterpret_cast<char *>(&c_double), sizeof(c_double));
2352
2353 result.add_to_coeff(idx, c);
2354 }
2355
2356 return result;
2357 }
2358
2359 // =================================================================
2360 // Phase 2 — Parallelism
2361 // =================================================================
2362
2373 {
2374 const size_t n = pts.size();
2376
2377 // Sequential evaluation (parallelism can be added via pmaps in future)
2378 for (size_t i = 0; i < n; ++i)
2379 results(i) = eval(pts(i));
2380
2381 return results;
2382 }
2383
2399 const Array<std::pair<Array<Coefficient>, Coefficient>> &data,
2400 size_t nvars,
2401 const Array<Array<size_t>> &basis)
2402 {
2403 const size_t m = data.size();
2404 const size_t b = basis.size();
2405
2406 ah_domain_error_if(m == 0) << "fit_parallel: data is empty";
2407 ah_domain_error_if(b == 0) << "fit_parallel: basis is empty";
2408
2409 // Convert data to std::vector for pmaps
2410 std::vector<std::pair<Array<Coefficient>, Coefficient>> data_vec(m);
2411 for (size_t i = 0; i < m; ++i)
2412 data_vec[i] = data(i);
2413
2414 // Convert basis to std::vector for capture
2415 std::vector<Array<size_t>> basis_vec(b);
2416 for (size_t i = 0; i < b; ++i)
2417 basis_vec[i] = basis(i);
2418
2419 // Build design matrix rows in parallel
2420 auto rows = pmaps(data_vec,
2421 [&basis_vec, b](const std::pair<Array<Coefficient>, Coefficient> &p)
2422 {
2424 for (size_t j = 0; j < b; ++j)
2425 row(j) = multi_poly_detail::eval_monomial(p.first, basis_vec[j]);
2426 return row;
2427 });
2428
2429 // Normal equations (sequential — bottleneck is row construction)
2432 for (size_t i = 0; i < m; ++i)
2433 {
2434 A(i) = rows[i];
2435 y(i) = data(i).second;
2436 }
2437
2438 // Compute A^T A and A^T y
2441
2442 for (size_t i = 0; i < b; ++i)
2443 {
2444 for (size_t j = 0; j < b; ++j)
2445 {
2446 AtA(i)(j) = Coefficient{};
2447 for (size_t k = 0; k < m; ++k)
2448 AtA(i)(j) += A(k)(i) * A(k)(j);
2449 }
2450
2451 Aty(i) = Coefficient{};
2452 for (size_t k = 0; k < m; ++k)
2453 Aty(i) += A(k)(i) * y(k);
2454 }
2455
2456 // Gaussian elimination with pivoting
2457 Array<Coefficient> c(b);
2458 for (size_t col = 0; col < b; ++col)
2459 {
2460 size_t prow = col;
2461 Coefficient pval = std::abs(AtA(col)(col));
2462 for (size_t row = col + 1; row < b; ++row)
2463 {
2464 Coefficient av = std::abs(AtA(row)(col));
2465 if (av > pval)
2466 {
2467 pval = av;
2468 prow = row;
2469 }
2470 }
2471
2473 << "fit_parallel: singular normal equations";
2474
2475 if (prow != col)
2476 {
2477 std::swap(AtA(col), AtA(prow));
2478 std::swap(Aty(col), Aty(prow));
2479 }
2480
2481 for (size_t row = col + 1; row < b; ++row)
2482 {
2483 Coefficient f = AtA(row)(col) / AtA(col)(col);
2484 for (size_t j = col; j < b; ++j)
2485 AtA(row)(j) -= f * AtA(col)(j);
2486 Aty(row) -= f * Aty(col);
2487 }
2488 }
2489
2490 // Back-substitution
2491 for (size_t i = b; i > 0; --i)
2492 {
2493 c(i - 1) = Aty(i - 1);
2494 for (size_t j = i; j < b; ++j)
2495 c(i - 1) -= AtA(i - 1)(j) * c(j);
2496 c(i - 1) /= AtA(i - 1)(i - 1);
2497 }
2498
2499 // Assemble polynomial
2500 Gen_MultiPolynomial result(nvars);
2501 for (size_t j = 0; j < b; ++j)
2503 result.add_to_coeff(basis(j), c(j));
2504
2505 return result;
2506 }
2507
2508 // =================================================================
2509 // Layer 3 — Gröbner Bases and Ideal Operations
2510 // =================================================================
2511
2531 [[nodiscard]] std::pair<Array<Gen_MultiPolynomial>, Gen_MultiPolynomial> divmod(
2533 {
2534 size_t s = divisors.size();
2535 ah_domain_error_if(s == 0) << "divmod: empty divisors array";
2536
2537 for (size_t i = 0; i < s; ++i)
2538 {
2539 ah_domain_error_if(divisors(i).is_zero()) << "divmod: divisors[" << i << "] is zero";
2541 << "divmod: divisors[" << i << "].nvars (" << divisors(i).nvars_ << ") != this.nvars ("
2542 << nvars_ << ")";
2543 }
2544
2545 // Initialize quotient array and remainder
2548 Gen_MultiPolynomial p = *this;
2549
2550 // Buchberger's multivariate division algorithm
2551 while (not p.is_zero())
2552 {
2553 auto [lm_p, lc_p] = p.leading_term();
2554 bool found = false;
2555
2556 for (size_t i = 0; i < s; ++i)
2557 {
2558 if (divisors(i).is_zero())
2559 continue;
2560
2561 auto [lm_fi, lc_fi] = divisors(i).leading_term();
2562
2564 {
2565 if constexpr (std::is_integral_v<Coefficient>)
2566 {
2567 if (lc_p % lc_fi != Coefficient{})
2568 continue;
2569 }
2570
2574 continue;
2575 q(i).add_to_coeff(t_idx, t_coeff);
2576 p -= monomial(nvars_, t_idx, t_coeff) * divisors(i);
2577 found = true;
2578 break;
2579 }
2580 }
2581
2582 if (not found)
2583 {
2584 r.add_to_coeff(lm_p, lc_p);
2585 p -= monomial(nvars_, lm_p, lc_p);
2586 }
2587 }
2588
2589 return {q, r};
2590 }
2591
2607 const Gen_MultiPolynomial &g)
2608 requires(not std::is_integral_v<Coefficient>)
2609 {
2610 ah_domain_error_if(f.is_zero()) << "s_poly: first argument is zero";
2611 ah_domain_error_if(g.is_zero()) << "s_poly: second argument is zero";
2612 ah_invalid_argument_if(f.nvars_ != g.nvars_)
2613 << "s_poly: incompatible number of variables (" << f.nvars_ << " vs " << g.nvars_ << ")";
2614
2615 const size_t nv = f.nvars_;
2616 auto [lm_f, lc_f] = f.leading_term();
2617 auto [lm_g, lc_g] = g.leading_term();
2618
2620
2624
2628
2629 return t_f * f - t_g * g;
2630 }
2631
2659 requires(not std::is_integral_v<Coefficient>)
2660 {
2661 const size_t gen_count = generators.size();
2662 ah_domain_error_if(gen_count == 0) << "groebner_basis: empty generators";
2663
2664 for (size_t i = 0; i < gen_count; ++i)
2665 ah_domain_error_if(generators(i).is_zero()) << "groebner_basis: generator[" << i << "] is zero";
2666
2668
2669 ah_domain_error_if(G.is_empty()) << "groebner_basis: generators reduce to the zero basis";
2670
2672 Pair_Queue P;
2676
2677 size_t iteration = 0;
2678
2679 // Main Buchberger loop
2680 while (not P.is_empty())
2681 {
2682 constexpr size_t max_iterations = 10000;
2683 ah_domain_error_if(++iteration > max_iterations)
2684 << "groebner_basis: iteration limit (" << max_iterations << ") exceeded";
2685
2687 const size_t i = candidate.i;
2688 const size_t j = candidate.j;
2689
2690 // B1 criterion: if LMs are coprime (all min(lm_i, lm_j) = 0), skip
2693
2695 {
2696 zero_pairs.insert(canonical_pair(i, j), true);
2697 continue;
2698 }
2699
2701 {
2702 zero_pairs.insert(canonical_pair(i, j), true);
2703 continue;
2704 }
2705
2706 // Reduce S-polynomial
2707 Gen_MultiPolynomial s = s_poly(G(i), G(j));
2708 auto [qs, rs] = s.divmod(G);
2710
2711 // If remainder is non-zero, add to basis and pair with all existing elements
2712 if (not r.is_zero())
2713 {
2716 continue;
2717
2718 G.append(std::move(r));
2719 G = autoreduce_groebner_basis(std::move(G));
2721 << "groebner_basis: incremental autoreduction collapsed the basis";
2723 continue;
2724 }
2725 zero_pairs.insert(canonical_pair(i, j), true);
2726 }
2727
2728 return G;
2729 }
2730
2748 requires(not std::is_integral_v<Coefficient>)
2749 {
2750 // Phase 1: Compute Gröbner basis and drop redundant leading monomials.
2752
2753 // Phase 2: Make monic
2754 for (size_t i = 0; i < G.size(); ++i)
2756
2757 // Phase 3: Interreduce
2758 for (size_t i = 0; i < G.size(); ++i)
2759 {
2760 if (G.size() == 1)
2761 continue; // Skip if only one element
2762
2763 // Build G_minus_i: all elements except i
2765 size_t idx = 0;
2766 for (size_t j = 0; j < G.size(); ++j)
2767 if (j != i)
2768 G_minus_i(idx++) = G(j);
2769
2770 if (not G_minus_i.is_empty())
2771 {
2772 auto [q, r] = G(i).divmod(G_minus_i);
2773 G(i) = std::move(r);
2775 }
2776 }
2777
2779 for (size_t i = 0; i < G.size(); ++i)
2781
2782 return minimize_groebner_basis(std::move(G));
2783 }
2784
2799
2817 requires(not std::is_integral_v<Coefficient>)
2818 {
2819 if (f.is_zero())
2820 return true;
2821 return f.divmod(groebner_basis(generators)).second.is_zero();
2822 }
2823
2824 // =================================================================
2825 // Layer 4 — Ideal Arithmetic
2826 // =================================================================
2827
2843 {
2844 ah_domain_error_if(a.size() == 0) << "ideal_sum: first array is empty";
2845 ah_domain_error_if(b.size() == 0) << "ideal_sum: second array is empty";
2846
2847 for (size_t i = 0; i < a.size(); ++i)
2848 ah_domain_error_if(a(i).is_zero()) << "ideal_sum: a[" << i << "] is zero";
2849
2850 for (size_t j = 0; j < b.size(); ++j)
2851 ah_domain_error_if(b(j).is_zero()) << "ideal_sum: b[" << j << "] is zero";
2852
2853 size_t nv = a(0).nvars_;
2854 for (size_t i = 1; i < a.size(); ++i)
2855 ah_invalid_argument_if(a(i).nvars_ != nv) << "ideal_sum: nvars mismatch in a[" << i
2856 << "]: expected " << nv << ", got " << a(i).nvars_;
2857
2858 for (size_t j = 0; j < b.size(); ++j)
2859 ah_invalid_argument_if(b(j).nvars_ != nv) << "ideal_sum: nvars mismatch in b[" << j
2860 << "]: expected " << nv << ", got " << b(j).nvars_;
2861
2863 for (size_t i = 0; i < a.size(); ++i)
2864 result(i) = a(i);
2865 for (size_t j = 0; j < b.size(); ++j)
2866 result(a.size() + j) = b(j);
2867
2868 return result;
2869 }
2870
2886 {
2887 ah_domain_error_if(a.size() == 0) << "ideal_product: first array is empty";
2888 ah_domain_error_if(b.size() == 0) << "ideal_product: second array is empty";
2889
2890 for (size_t i = 0; i < a.size(); ++i)
2891 ah_domain_error_if(a(i).is_zero()) << "ideal_product: a[" << i << "] is zero";
2892
2893 for (size_t j = 0; j < b.size(); ++j)
2894 ah_domain_error_if(b(j).is_zero()) << "ideal_product: b[" << j << "] is zero";
2895
2896 size_t nv = a(0).nvars_;
2897 for (size_t i = 1; i < a.size(); ++i)
2898 ah_invalid_argument_if(a(i).nvars_ != nv) << "ideal_product: nvars mismatch in a[" << i
2899 << "]: expected " << nv << ", got " << a(i).nvars_;
2900
2901 for (size_t j = 0; j < b.size(); ++j)
2902 ah_invalid_argument_if(b(j).nvars_ != nv) << "ideal_product: nvars mismatch in b[" << j
2903 << "]: expected " << nv << ", got " << b(j).nvars_;
2904
2906 size_t k = 0;
2907 for (size_t i = 0; i < a.size(); ++i)
2908 for (size_t j = 0; j < b.size(); ++j)
2909 result(k++) = a(i) * b(j);
2910
2911 return result;
2912 }
2913
2928 size_t n)
2929 {
2930 ah_domain_error_if(gens.size() == 0) << "ideal_power: empty generators";
2931
2932 for (size_t i = 0; i < gens.size(); ++i)
2933 ah_domain_error_if(gens(i).is_zero()) << "ideal_power: gens[" << i << "] is zero";
2934
2935 size_t nv = gens(0).nvars_;
2936
2937 // Special case: I^0 = ⟨1⟩
2938 if (n == 0)
2939 {
2942 return unit;
2943 }
2944
2945 // Special case: I^1 = I
2946 if (n == 1)
2947 return gens;
2948
2949 // General case: iteratively multiply
2951 for (size_t i = 1; i < n; ++i)
2952 result = ideal_product(result, gens);
2953
2954 return result;
2955 }
2956
2975 requires(not std::is_integral_v<Coefficient>)
2976 {
2977 ah_domain_error_if(I_gens.size() == 0) << "contains_ideal: I is empty";
2978 ah_domain_error_if(J_gens.size() == 0) << "contains_ideal: J is empty";
2979
2980 for (size_t i = 0; i < I_gens.size(); ++i)
2981 ah_domain_error_if(I_gens(i).is_zero()) << "contains_ideal: I[" << i << "] is zero";
2982
2983 for (size_t j = 0; j < J_gens.size(); ++j)
2984 ah_domain_error_if(J_gens(j).is_zero()) << "contains_ideal: J[" << j << "] is zero";
2985
2986 size_t nv = I_gens(0).nvars_;
2987 for (size_t i = 1; i < I_gens.size(); ++i)
2989 << "contains_ideal: nvars mismatch in I[" << i << "]: expected " << nv << ", got "
2990 << I_gens(i).nvars_;
2991
2992 for (size_t j = 0; j < J_gens.size(); ++j)
2994 << "contains_ideal: nvars mismatch in J[" << j << "]: expected " << nv << ", got "
2995 << J_gens(j).nvars_;
2996
2997 // Precompute Gröbner basis of I once
2999
3000 // Check membership of each generator of J in I
3001 for (size_t j = 0; j < J_gens.size(); ++j)
3002 {
3004 if (not remainder.is_zero())
3005 return false;
3006 }
3007
3008 return true;
3009 }
3010
3027 requires(not std::is_integral_v<Coefficient>)
3028 {
3029 ah_domain_error_if(I_gens.size() == 0) << "ideals_equal: I is empty";
3030 ah_domain_error_if(J_gens.size() == 0) << "ideals_equal: J is empty";
3031
3032 for (size_t i = 0; i < I_gens.size(); ++i)
3033 ah_domain_error_if(I_gens(i).is_zero()) << "ideals_equal: I[" << i << "] is zero";
3034
3035 for (size_t j = 0; j < J_gens.size(); ++j)
3036 ah_domain_error_if(J_gens(j).is_zero()) << "ideals_equal: J[" << j << "] is zero";
3037
3038 size_t nv = I_gens(0).nvars_;
3039 for (size_t i = 1; i < I_gens.size(); ++i)
3041 << "ideals_equal: nvars mismatch in I[" << i << "]: expected " << nv << ", got "
3042 << I_gens(i).nvars_;
3043
3044 for (size_t j = 0; j < J_gens.size(); ++j)
3046 << "ideals_equal: nvars mismatch in J[" << j << "]: expected " << nv << ", got "
3047 << J_gens(j).nvars_;
3048
3050 }
3051
3071 requires(not std::is_integral_v<Coefficient>)
3072 {
3073 ah_domain_error_if(gens.size() == 0) << "radical_member: empty generators";
3074
3075 for (size_t i = 0; i < gens.size(); ++i)
3076 ah_domain_error_if(gens(i).is_zero()) << "radical_member: gens[" << i << "] is zero";
3077
3078 size_t nv = gens(0).nvars_;
3079
3080 // Check nvars consistency (f.nvars_ == 0 is OK for zero polynomial)
3081 ah_invalid_argument_if(not f.is_zero() and f.nvars_ != nv)
3082 << "radical_member: f nvars mismatch: expected " << nv << ", got " << f.nvars_;
3083
3084 // Special case: 0 ∈ √I always
3085 if (f.is_zero())
3086 return true;
3087
3088 // Augmented ring has nv+1 variables (the new variable y is at index nv)
3089 size_t nv_aug = nv + 1;
3090
3091 // Promote generators to the augmented ring
3093 for (size_t i = 0; i < gens.size(); ++i)
3094 aug_gens(i) = gens(i).promote(nv_aug);
3095
3096 // Create the polynomial (1 - y·f) in the augmented ring
3097 // y = monomial with exponent [0, ..., 0, 1] at index nv
3098 Array<size_t> y_exp(nv_aug, static_cast<size_t>(0));
3099 y_exp(nv) = 1;
3101
3102 // Create 1 in the augmented ring
3104
3105 // Promote f to augmented ring and compute (1 - y·f)
3107 aug_gens(gens.size()) = one_aug - y_poly * f_aug;
3108
3109 // Check if 1 is in the augmented ideal
3110 return ideal_member(one_aug, aug_gens);
3111 }
3112
3113 // =================================================================
3114 // Layer 6 — Multivariate Factorization
3115 // =================================================================
3116
3127
3144 {
3145 if (coeffs.is_empty())
3146 return Coefficient{};
3147
3148 Coefficient result = Coefficient{};
3149 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
3150 {
3151 const auto &p = it.get_curr();
3152 Coefficient a = p.second < Coefficient{} ? -p.second : p.second;
3153 result = std::gcd(result, a);
3154 }
3155
3156 // Sign follows leading coefficient
3157 if (not is_zero())
3158 {
3160 if (lc < Coefficient{} and result > Coefficient{})
3161 result = -result;
3162 }
3163
3164 return result;
3165 }
3166
3180 {
3181 if (is_zero())
3183
3184 Coefficient c = content();
3185 if (c == Coefficient{})
3186 return *this;
3187
3188 if (c == Coefficient(1))
3189 return *this;
3190
3191 if (c == Coefficient(-1))
3192 {
3194 for_each_term([&](const Array<size_t> &idx, const Coefficient &coeff)
3195 {
3196 r.coeffs.insert(idx, -coeff);
3197 });
3198 return r;
3199 }
3200
3202 for_each_term([&](const Array<size_t> &idx, const Coefficient &coeff)
3203 {
3204 if (Coefficient q = coeff / c; not coeff_is_zero(q))
3205 result.coeffs.insert(idx, q);
3206 });
3207 return result;
3208 }
3209
3226 const Array<Coefficient> &eval_pts) const
3227 {
3229 << "homomorphic_eval: keep_var " << keep_var << " >= nvars " << nvars_;
3230 ah_domain_error_if(eval_pts.size() != nvars_ - 1)
3231 << "homomorphic_eval: eval_pts size " << eval_pts.size() << " != nvars-1 " << (nvars_ - 1);
3232
3234
3235 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
3236 {
3238 size_t pt_idx = 0;
3239 for (size_t v = 0; v < nvars_; ++v)
3240 {
3241 if (v == keep_var)
3242 continue;
3243 if (idx(v) != 0)
3245 ++pt_idx;
3246 }
3247
3249 result.set_coeff(idx(keep_var), result.get_coeff(idx(keep_var)) + term_val);
3250 });
3251
3252 return result;
3253 }
3254
3263 size_t nv,
3264 const size_t x_var)
3265 {
3266 Gen_MultiPolynomial result(nv);
3267 u.for_each_term([&](const size_t exp, const Coefficient &c)
3268 {
3269 Array<size_t> idx(nv, size_t{0});
3270 idx(x_var) = exp;
3271 result.add_to_coeff(idx, c);
3272 });
3273 return result;
3274 }
3275
3278 {
3279 for (size_t i = 0; i < vals.size(); ++i)
3280 if (vals(i) == value)
3281 return;
3282 vals.append(value);
3283 }
3284
3291
3294 {
3296 {
3297 if (lhs.main_coeff < rhs.main_coeff)
3298 return true;
3299 if (rhs.main_coeff < lhs.main_coeff)
3300 return false;
3301 return lhs.constant_term < rhs.constant_term;
3302 }
3303 };
3304
3308 requires std::is_integral_v<Coefficient>
3309 {
3311
3313 auto factors = u.factorize();
3314 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3315 {
3316 const auto &term = it.get_curr();
3317 if (term.factor.degree() != 1)
3318 continue;
3319
3320 UniPoly primitive = term.factor.primitive_part();
3321 for (size_t m = 0; m < term.multiplicity; ++m)
3322 result.append(Uni_Linear_Factor_Data{primitive.leading_coeff(), primitive.get_coeff(0)});
3323 }
3324
3325 if (result.size() > 1)
3326 Aleph::in_place_sort(result, Uni_Linear_Factor_Data_Less{});
3327
3328 return result;
3329 }
3330
3334 requires std::is_integral_v<Coefficient>
3335 {
3336 Array<Coefficient> roots;
3337 auto factors = u.factorize();
3338 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3339 {
3340 const auto &term = it.get_curr();
3341 if (term.factor.degree() != 1 or term.factor.get_coeff(1) != Coefficient(1))
3342 continue;
3343 _append_unique(roots, -term.factor.get_coeff(0));
3344 }
3345 return roots;
3346 }
3347
3349 [[nodiscard]] static size_t _eval_index_for_var(size_t main_var, size_t var) noexcept
3350 {
3351 return var < main_var ? var : var - 1;
3352 }
3353
3356 {
3357 size_t count = 0;
3358 last_active_var = 0;
3359 for (size_t v = 0; v < p.num_vars(); ++v)
3360 if (p.degree_in(v) > 0)
3361 {
3362 ++count;
3363 last_active_var = v;
3364 }
3365 return count;
3366 }
3367
3379 size_t nvars,
3380 size_t main_var,
3383 const Coefficient &main_coeff,
3386 requires std::is_integral_v<Coefficient>
3387 {
3388 Gen_MultiPolynomial factor(nvars);
3389
3390 Array<size_t> idx(nvars, size_t{0});
3391 idx(main_var) = 1;
3392 factor.add_to_coeff(idx, main_coeff);
3393
3395 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3396 {
3397 const Coefficient coeff = other_coeffs(pos);
3398 intercept -= coeff * base_eval_pts(_eval_index_for_var(main_var, other_vars(pos)));
3399
3400 if (coeff_is_zero(coeff))
3401 continue;
3402
3403 Array<size_t> var_idx(nvars, size_t{0});
3404 var_idx(other_vars(pos)) = 1;
3405 factor.add_to_coeff(var_idx, coeff);
3406 }
3407
3408 if (not coeff_is_zero(intercept))
3409 factor.add_to_coeff(Array<size_t>(nvars, size_t{0}), intercept);
3410
3411 return factor;
3412 }
3413
3416 size_t main_var,
3418 Gen_MultiPolynomial &factor)
3419 requires std::is_integral_v<Coefficient>
3420 {
3422
3423 UniPoly base_univ = p.homomorphic_eval(main_var, base_eval_pts);
3424 Array<Uni_Linear_Factor_Data> base_factors = _collect_sorted_primitive_linear_factors(base_univ);
3425 if (base_factors.is_empty())
3426 return false;
3427
3429 for (size_t v = 0; v < p.num_vars(); ++v)
3430 if (v != main_var and p.degree_in(v) > 0)
3431 other_vars.append(v);
3432
3433 if (other_vars.is_empty())
3434 return false;
3435
3438 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3439 {
3441 eval_pts(_eval_index_for_var(main_var, other_vars(pos))) += Coefficient(1);
3442
3443 UniPoly perturbed = p.homomorphic_eval(main_var, eval_pts);
3444 perturbed_factors(pos) = _collect_sorted_primitive_linear_factors(perturbed);
3445 if (perturbed_factors(pos).size() != base_factors.size())
3446 return false;
3447 }
3448
3449 for (size_t slot = 0; slot < base_factors.size(); ++slot)
3450 {
3451 const Coefficient main_coeff = base_factors(slot).main_coeff;
3452 const Coefficient base_constant_term = base_factors(slot).constant_term;
3454
3455 bool consistent = true;
3456 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3457 {
3458 const auto &perturbed = perturbed_factors(pos)(slot);
3459 if (perturbed.main_coeff != main_coeff)
3460 {
3461 consistent = false;
3462 break;
3463 }
3464
3465 other_coeffs(pos) = perturbed.constant_term - base_constant_term;
3466 }
3467
3468 if (not consistent)
3469 continue;
3470
3471 Gen_MultiPolynomial candidate = _build_affine_factor_from_linear_data(p.num_vars(),
3472 main_var,
3473 other_vars,
3475 main_coeff,
3478
3479 if (candidate.is_constant())
3480 continue;
3481
3482 if (_divides_exactly(p, candidate))
3483 {
3484 factor = std::move(candidate);
3485 return true;
3486 }
3487 }
3488
3489 return false;
3490 }
3491
3499 size_t main_var,
3503 const Coefficient &base_root)
3504 requires std::is_integral_v<Coefficient>
3505 {
3506 Gen_MultiPolynomial factor(nvars);
3507
3508 Array<size_t> idx(nvars, size_t{0});
3509 idx(main_var) = 1;
3510 factor.add_to_coeff(idx, Coefficient(1));
3511
3513 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3514 {
3515 intercept -= slopes(pos) * base_eval_pts(_eval_index_for_var(main_var, other_vars(pos)));
3516
3517 if (coeff_is_zero(slopes(pos)))
3518 continue;
3519
3520 Array<size_t> var_idx(nvars, size_t{0});
3521 var_idx(other_vars(pos)) = 1;
3522 factor.add_to_coeff(var_idx, -slopes(pos));
3523 }
3524
3525 if (not coeff_is_zero(intercept))
3526 factor.add_to_coeff(Array<size_t>(nvars, size_t{0}), -intercept);
3527
3528 return factor;
3529 }
3530
3533 size_t main_var,
3537 const Coefficient &base_root,
3538 size_t pos,
3540 Gen_MultiPolynomial &factor)
3541 requires std::is_integral_v<Coefficient>
3542 {
3543 if (pos == other_vars.size())
3544 {
3546 = _build_affine_factor(p.num_vars(), main_var, other_vars, base_eval_pts, slopes, base_root);
3547
3548 if (candidate.is_constant())
3549 return false;
3550
3551 if (_divides_exactly(p, candidate))
3552 {
3553 factor = std::move(candidate);
3554 return true;
3555 }
3556
3557 return false;
3558 }
3559
3560 for (size_t i = 0; i < root_options(pos).size(); ++i)
3561 {
3562 slopes(pos) = root_options(pos)(i) - base_root;
3563 if (_search_affine_lift_options(
3565 return true;
3566 }
3567
3568 return false;
3569 }
3570
3573 size_t main_var,
3575 Gen_MultiPolynomial &factor)
3576 requires std::is_integral_v<Coefficient>
3577 {
3579
3580 if (_try_lift_primitive_affine_linear_factor_for_main_var(p, main_var, base_eval_pts, factor))
3581 return true;
3582
3583 UniPoly base_univ = p.homomorphic_eval(main_var, base_eval_pts);
3584 Array<Coefficient> base_roots = _collect_unique_monic_linear_roots(base_univ);
3585 if (base_roots.is_empty())
3586 return false;
3587
3589 for (size_t v = 0; v < p.num_vars(); ++v)
3590 if (v != main_var and p.degree_in(v) > 0)
3591 other_vars.append(v);
3592
3593 if (other_vars.is_empty())
3594 return false;
3595
3597 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3598 {
3600 eval_pts(_eval_index_for_var(main_var, other_vars(pos))) += Coefficient(1);
3601
3602 UniPoly perturbed = p.homomorphic_eval(main_var, eval_pts);
3603 root_options(pos) = _collect_unique_monic_linear_roots(perturbed);
3604 if (root_options(pos).is_empty())
3605 return false;
3606 }
3607
3609 for (size_t i = 0; i < base_roots.size(); ++i)
3610 if (_search_affine_lift_options(
3612 return true;
3613
3614 return false;
3615 }
3616
3619 Gen_MultiPolynomial &factor)
3620 requires std::is_integral_v<Coefficient>
3621 {
3622 for (size_t main_var = 0; main_var < p.num_vars(); ++main_var)
3623 {
3624 if (p.degree_in(main_var) == 0)
3625 continue;
3626
3627 Array<Coefficient> base_eval_pts(p.num_vars() - 1, Coefficient{});
3628 if (_try_lift_affine_linear_factor_for_main_var(p, main_var, base_eval_pts, factor))
3629 return true;
3630
3631 Coefficient val(1);
3632 for (size_t v = 0; v < p.num_vars(); ++v)
3633 {
3634 if (v == main_var)
3635 continue;
3636 base_eval_pts(_eval_index_for_var(main_var, v)) = val;
3637 val = val + Coefficient(2);
3638 }
3639
3640 if (_try_lift_affine_linear_factor_for_main_var(p, main_var, base_eval_pts, factor))
3641 return true;
3642 }
3643
3644 return false;
3645 }
3646
3650 Coefficient start,
3653 size_t &total_points)
3654 requires std::is_integral_v<Coefficient>
3655 {
3656 static constexpr size_t max_grid_points = 81;
3657
3659 total_points = 1;
3660
3661 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3662 {
3663 const size_t degree_bound = p.degree_in(other_vars(pos));
3664 const size_t node_count = degree_bound + 1;
3665 if (node_count == 0)
3666 return false;
3667
3668 if (total_points > max_grid_points / node_count)
3669 return false;
3670 total_points *= node_count;
3671
3672 grid(pos).reserve(node_count);
3673 Coefficient value = start;
3674 for (size_t i = 0; i < node_count; ++i)
3675 {
3676 grid(pos).append(value);
3677 value = value + step;
3678 }
3679 }
3680
3681 return total_points > 0;
3682 }
3683
3686 size_t flat_index)
3687 {
3688 Array<Coefficient> point(grid.size(), Coefficient{});
3689 size_t remaining = flat_index;
3690 for (size_t pos = grid.size(); pos > 0; --pos)
3691 {
3692 const size_t idx = remaining % grid(pos - 1).size();
3693 remaining /= grid(pos - 1).size();
3694 point(pos - 1) = grid(pos - 1)(idx);
3695 }
3696 return point;
3697 }
3698
3701 size_t main_var,
3704 {
3706 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3707 eval_pts(_eval_index_for_var(main_var, other_vars(pos))) = coords(pos);
3708 return eval_pts;
3709 }
3710
3714 requires std::is_integral_v<Coefficient>
3715 {
3717
3719 auto terms = u.factorize();
3720 for (auto it = terms.get_it(); it.has_curr(); it.next_ne())
3721 {
3722 const auto &term = it.get_curr();
3723 for (size_t m = 0; m < term.multiplicity; ++m)
3724 expanded.append(term.factor);
3725 }
3726 return expanded;
3727 }
3728
3731 {
3733 {
3734 if (lhs.degree() != rhs.degree())
3735 return lhs.degree() < rhs.degree();
3736
3737 const Coefficient lhs_lc = lhs.leading_coeff();
3738 const Coefficient rhs_lc = rhs.leading_coeff();
3739
3740 for (size_t exp = lhs.degree(); exp > 0; --exp)
3741 {
3742 const Coefficient lhs_coeff = lhs.get_coeff(exp - 1);
3743 const Coefficient rhs_coeff = rhs.get_coeff(exp - 1);
3744
3745 const __int128 lhs_norm = static_cast<__int128>(lhs_coeff) * rhs_lc;
3746 const __int128 rhs_norm = static_cast<__int128>(rhs_coeff) * lhs_lc;
3747 if (lhs_norm < rhs_norm)
3748 return true;
3749 if (rhs_norm < lhs_norm)
3750 return false;
3751 }
3752
3753 return lhs_lc < rhs_lc;
3754 }
3755 };
3756
3759 size_t max_degree,
3761 requires std::is_integral_v<Coefficient>
3762 {
3764
3766
3767 Array<UniPoly> expanded = _expanded_univariate_factors(u);
3768 for (size_t i = 0; i < expanded.size(); ++i)
3769 {
3770 UniPoly factor = expanded(i);
3771 if (factor.is_zero() or factor.is_constant())
3772 continue;
3773 factor = factor.primitive_part();
3774 groups(factor.degree()).append(factor);
3775 }
3776
3778 for (size_t degree = 0; degree < groups.size(); ++degree)
3779 if (groups(degree).size() > 1)
3781
3782 return true;
3783 }
3784
3788 size_t nvars,
3789 size_t main_var,
3791 size_t exp)
3792 {
3793 Gen_MultiPolynomial result(nvars);
3794 coeff_poly.for_each_term([&](const Array<size_t> &idx_sub, const Coefficient &c)
3795 {
3796 Array<size_t> idx_full(nvars, size_t{0});
3798 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3799 idx_full(other_vars(pos)) = idx_sub(pos);
3800 result.add_to_coeff(idx_full, c);
3801 });
3802 return result;
3803 }
3804
3815 requires std::is_integral_v<Coefficient>
3816 {
3818
3820
3822 for (size_t v = 0; v < p.num_vars(); ++v)
3823 if (v != main_var and p.degree_in(v) > 0)
3824 other_vars.append(v);
3825
3826 if (other_vars.is_empty())
3827 return result;
3828
3830 size_t total_points = 0;
3831 if (not _build_interpolation_grid(p, other_vars, grid_start, grid_step, grid, total_points))
3832 return result;
3833
3835 {
3836 Array<Coefficient> base_coords = _decode_grid_point(grid, 0);
3838 = _build_eval_point(p.num_vars(), main_var, other_vars, base_coords);
3839 UniPoly base_univ = p.homomorphic_eval(main_var, eval_pts);
3840 if (base_univ.is_zero() or base_univ.is_constant())
3841 return result;
3842 if (not _collect_sorted_primitive_factor_groups(base_univ, p.degree_in(main_var), base_groups))
3843 return result;
3844 }
3845
3848 for (size_t degree = 1; degree < base_groups.size(); ++degree)
3849 for (size_t slot = 0; slot < base_groups(degree).size(); ++slot)
3850 {
3851 target_degrees.append(degree);
3852 target_slots.append(slot);
3853 }
3854
3855 if (target_degrees.is_empty())
3856 return result;
3857
3860 for (size_t t = 0; t < target_degrees.size(); ++t)
3861 {
3862 const size_t degree = target_degrees(t);
3864 for (size_t exp = 0; exp <= degree; ++exp)
3866 }
3867
3868 for (size_t flat = 0; flat < total_points; ++flat)
3869 {
3870 Array<Coefficient> coords = _decode_grid_point(grid, flat);
3871 Array<Coefficient> eval_pts = _build_eval_point(p.num_vars(), main_var, other_vars, coords);
3872 UniPoly u = p.homomorphic_eval(main_var, eval_pts);
3873 if (u.is_zero() or u.is_constant())
3875
3877 if (not _collect_sorted_primitive_factor_groups(u, p.degree_in(main_var), curr_groups))
3879
3880 for (size_t degree = 0; degree < base_groups.size(); ++degree)
3881 if (curr_groups(degree).size() != base_groups(degree).size())
3883
3884 for (size_t t = 0; t < target_degrees.size(); ++t)
3885 {
3886 const size_t degree = target_degrees(t);
3887 const size_t slot = target_slots(t);
3888 const UniPoly &match = curr_groups(degree)(slot);
3889
3890 for (size_t exp = 0; exp <= degree; ++exp)
3891 coeff_samples(t)(exp)(flat) = match.get_coeff(exp);
3892 }
3893 }
3894
3895 for (size_t t = 0; t < target_degrees.size(); ++t)
3896 {
3897 const size_t degree = target_degrees(t);
3898 Gen_MultiPolynomial candidate(p.num_vars());
3899 for (size_t exp = 0; exp <= degree; ++exp)
3900 {
3903 candidate
3904 += _embed_coeff_poly_as_x_term(coeff_poly, p.num_vars(), main_var, other_vars, exp);
3905 }
3906
3907 if (_divides_exactly(p, candidate))
3908 result.append(std::move(candidate));
3909 }
3910
3911 return result;
3912 }
3913
3916 Gen_MultiPolynomial &factor)
3917 requires std::is_integral_v<Coefficient>
3918 {
3919 for (size_t main_var = 0; main_var < p.num_vars(); ++main_var)
3920 {
3921 if (p.degree_in(main_var) == 0)
3922 continue;
3923
3924 for (Coefficient grid_start :
3927 {
3929 = _interpolated_factors_for_main_var(p, main_var, grid_start, grid_step);
3930 for (size_t i = 0; i < candidates.size(); ++i)
3931 if (not candidates(i).is_constant())
3932 {
3933 factor = std::move(candidates(i));
3934 return true;
3935 }
3936 }
3937 }
3938
3939 return false;
3940 }
3941
3949 size_t active_var)
3950 requires std::is_integral_v<Coefficient>
3951 {
3953
3954 UniPoly univ;
3955 p.for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
3956 {
3957 const size_t exp = idx(active_var);
3958 univ.set_coeff(exp, univ.get_coeff(exp) + c);
3959 });
3960
3961 DynList<FactorTerm> result;
3962 auto sfd_terms = univ.factorize();
3963 for (auto it = sfd_terms.get_it(); it.has_curr(); it.next_ne())
3964 {
3965 const auto &st = it.get_curr();
3966 result.append(
3967 FactorTerm{_embed_univariate(st.factor, p.num_vars(), active_var), st.multiplicity});
3968 }
3969 return result;
3970 }
3971
3989 {
3990 DynList<FactorTerm> result;
3991 const size_t k = candidates.size();
3992 if (k == 0)
3993 {
3994 if (not f.is_zero() and not f.is_constant())
3995 result.append(FactorTerm{f, 1});
3996 return result;
3997 }
3998
3999 if (k == 1)
4000 {
4001 if (not f.is_zero() and not f.is_constant())
4002 result.append(FactorTerm{f, 1});
4003 return result;
4004 }
4005
4006 // Track which candidates have been used
4007 Array<bool> used(k, false);
4008
4009 // Try subsets of increasing size (1, 2, ..., k/2)
4010 bool found_factor = true;
4011 while (found_factor)
4012 {
4013 found_factor = false;
4014
4015 // Count unused candidates
4016 size_t unused = 0;
4017 for (size_t i = 0; i < k; ++i)
4018 if (not used(i))
4019 ++unused;
4020
4021 if (unused <= 1)
4022 break;
4023
4024 // Try single candidates first (most common case)
4025 for (size_t i = 0; i < k; ++i)
4026 {
4027 if (used(i))
4028 continue;
4029
4030 const auto &cand = candidates(i);
4031 if (cand.is_zero() or cand.is_constant())
4032 {
4033 used(i) = true;
4034 continue;
4035 }
4036
4037 // Check if candidate divides f
4038 if (_divides_exactly(f, cand))
4039 {
4040 result.append(FactorTerm{cand, 1});
4041 f = _exact_quotient(f, cand);
4042 used(i) = true;
4043 found_factor = true;
4044
4045 if (f.is_constant())
4046 return result;
4047 break;
4048 }
4049 }
4050
4051 if (found_factor)
4052 continue;
4053
4054 // Try pairs of candidates
4055 for (size_t i = 0; i < k and not found_factor; ++i)
4056 {
4057 if (used(i))
4058 continue;
4059 for (size_t j = i + 1; j < k and not found_factor; ++j)
4060 {
4061 if (used(j))
4062 continue;
4063
4065 if (_divides_exactly(f, product))
4066 {
4067 result.append(FactorTerm{product, 1});
4068 f = _exact_quotient(f, product);
4069 used(i) = true;
4070 used(j) = true;
4071 found_factor = true;
4072
4073 if (f.is_constant())
4074 return result;
4075 }
4076 }
4077 }
4078 }
4079
4080 // Whatever remains of f is a factor
4081 if (not f.is_zero() and not f.is_constant())
4082 result.append(FactorTerm{f, 1});
4083
4084 return result;
4085 }
4086
4119 {
4120 DynList<FactorTerm> result;
4121
4122 if (is_zero() or is_constant())
4123 return result;
4124
4125 // Step 1: Extract content and compute primitive part
4126 const Coefficient scalar_content = content();
4127 Gen_MultiPolynomial remaining = primitive_part();
4128
4129 if (remaining.is_zero() or remaining.is_constant())
4130 return result;
4131
4132 if (scalar_content != Coefficient(1))
4134
4135 size_t active_var = 0;
4136 size_t active_vars = _count_active_vars(remaining, active_var);
4137
4138 // Special case: effectively univariate even if embedded in a larger ring.
4139 if (active_vars == 1)
4140 {
4141 DynList<FactorTerm> tail = _factorize_as_univariate_in_var(remaining, active_var);
4142 for (auto it = tail.get_it(); it.has_curr(); it.next_ne())
4143 result.append(it.get_curr());
4144 return result;
4145 }
4146
4147 // Step 2: Repeatedly lift exact factors from specializations when possible.
4148 while (true)
4149 {
4151 if (not _try_extract_affine_linear_factor(remaining, lifted_factor)
4152 and not _try_extract_interpolated_factor(remaining, lifted_factor))
4153 break;
4154
4155 size_t multiplicity = 1;
4156 remaining = _exact_quotient(remaining, lifted_factor);
4157 while (not remaining.is_zero() and not remaining.is_constant()
4158 and _divides_exactly(remaining, lifted_factor))
4159 {
4160 remaining = _exact_quotient(remaining, lifted_factor);
4161 ++multiplicity;
4162 }
4163
4164 result.append(FactorTerm{lifted_factor, multiplicity});
4165
4166 if (remaining.is_zero() or remaining.is_constant())
4167 return result;
4168
4169 active_vars = _count_active_vars(remaining, active_var);
4170 if (active_vars == 1)
4171 {
4172 DynList<FactorTerm> tail = _factorize_as_univariate_in_var(remaining, active_var);
4173 for (auto it = tail.get_it(); it.has_curr(); it.next_ne())
4174 result.append(it.get_curr());
4175 return result;
4176 }
4177 }
4178
4180
4181 // Step 3: Choose main variable among variables that actually appear.
4182 size_t main_var = nvars_;
4183 size_t min_deg = std::numeric_limits<size_t>::max();
4184 for (size_t v = 0; v < nvars_; ++v)
4185 {
4186 size_t dv = remaining.degree_in(v);
4187 if (dv == 0)
4188 continue;
4189
4190 if (dv < min_deg)
4191 {
4192 min_deg = dv;
4193 main_var = v;
4194 }
4195 }
4196
4197 ah_domain_error_if(main_var >= nvars_) << "factorize: primitive part has no active variable";
4198
4199 // Step 3: Choose evaluation points (small integers, avoiding roots)
4201 {
4202 Coefficient val(0);
4203 size_t pt_idx = 0;
4204 for (size_t v = 0; v < nvars_; ++v)
4205 {
4206 if (v == main_var)
4207 continue;
4208 eval_pts(pt_idx) = val;
4209 val = val + Coefficient(1);
4210 ++pt_idx;
4211 }
4212 }
4213
4214 // Step 4: Homomorphic evaluation to get univariate polynomial
4215 UniPoly g = remaining.homomorphic_eval(main_var, eval_pts);
4216
4217 if (g.is_zero() or g.is_constant())
4218 {
4219 // Evaluation collapsed; try different points
4220 {
4221 Coefficient val(1);
4222 size_t pt_idx = 0;
4223 for (size_t v = 0; v < nvars_; ++v)
4224 {
4225 if (v == main_var)
4226 continue;
4227 eval_pts(pt_idx) = val;
4228 val = val + Coefficient(2);
4229 ++pt_idx;
4230 }
4231 }
4232 g = remaining.homomorphic_eval(main_var, eval_pts);
4233
4234 if (g.is_zero() or g.is_constant())
4235 {
4236 // Still collapsed; return as irreducible
4237 result.append(FactorTerm{remaining, 1});
4238 return result;
4239 }
4240 }
4241
4242 // Step 5: Factor the univariate polynomial
4243 auto univ_sfd = g.factorize();
4244
4245 // Collect all factors, preserving multiplicity from the specialized image.
4247 for (auto it = univ_sfd.get_it(); it.has_curr(); it.next_ne())
4248 {
4249 const auto &term = it.get_curr();
4250 for (size_t m = 0; m < term.multiplicity; ++m)
4251 univ_factors.append(term.factor);
4252 }
4253
4254 if (_list_size(univ_factors) <= 1)
4255 {
4256 // Univariate is irreducible; original is likely irreducible
4257 result.append(FactorTerm{remaining, 1});
4258 return result;
4259 }
4260
4261 // Step 6: Embed univariate factors and try recombination
4262 size_t n_univ = _list_size(univ_factors);
4264 {
4265 size_t i = 0;
4266 for (auto it = univ_factors.get_it(); it.has_curr(); it.next_ne())
4267 candidates(i++) = _embed_univariate(it.get_curr(), nvars_, main_var);
4268 }
4269
4270 // Step 7: Factor recombination by trial division
4271 DynList<FactorTerm> tail = factor_recombination(remaining, candidates);
4272 for (auto it = tail.get_it(); it.has_curr(); it.next_ne())
4273 result.append(it.get_curr());
4274
4275 return result;
4276 }
4277
4278private:
4281 {
4282 Coefficient v = Coefficient(1);
4283 for (size_t j = 0; j < alpha.size(); ++j)
4284 if (alpha(j) != 0)
4285 v *= multi_poly_detail::int_power(pt(j), alpha(j));
4286 return v;
4287 }
4288
4289 // -----------------------------------------------------------------
4290 // Layer 6 private helpers
4291 // -----------------------------------------------------------------
4292
4295 size_t var,
4296 const Coefficient &val)
4297 {
4298 Gen_MultiPolynomial result(p.nvars_);
4299 p.for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
4300 {
4301 Coefficient coeff = c;
4302 if (idx(var) != 0)
4303 coeff *= multi_poly_detail::int_power(val, idx(var));
4304
4305 Array<size_t> new_idx = idx;
4306 new_idx(var) = 0;
4307 result.add_to_coeff(new_idx, coeff);
4308 });
4309 return result;
4310 }
4311
4313 template <typename T>
4314 static size_t _list_size(const DynList<T> &lst)
4315 {
4316 size_t n = 0;
4317 for (auto it = lst.get_it(); it.has_curr(); it.next_ne())
4318 ++n;
4319 return n;
4320 }
4321
4328 {
4329 if (divisor.is_zero())
4330 return false;
4331 if (f.is_zero())
4332 return true;
4333 if (divisor.degree() > f.degree())
4334 return false;
4335
4337 auto [q, r] = f.divmod(divisors);
4338 (void) q;
4339 return r.is_zero();
4340 }
4341
4349 {
4350 if (divisor.is_zero())
4351 return Gen_MultiPolynomial(f.num_vars());
4352
4354 auto [q, r] = f.divmod(divisors);
4355 ah_domain_error_if(not r.is_zero())
4356 << "_exact_quotient: divisor does not divide polynomial exactly";
4357 return q(0);
4358 }
4359
4360}; // class Gen_MultiPolynomial
4361
4362// ===================================================================
4363// Residual Analysis Helpers
4364// ===================================================================
4365
4376template <typename C>
4378{
4379 double r_squared = 0.0;
4380 double rmse = 0.0;
4381 double rss = 0.0;
4382 double tss = 0.0;
4383 double ess = 0.0;
4385 double mean_y = 0.0;
4386};
4387
4396template <typename C, class M>
4398 const Array<std::pair<Array<C>, C>> &data)
4399{
4400 PolyFitAnalysis<C> result;
4401 const size_t m = data.size();
4402
4403 if (m == 0)
4404 return result;
4405
4406 // Compute mean of y
4407 C sum_y = C{};
4408 for (size_t i = 0; i < m; ++i)
4409 sum_y += data(i).second;
4410 result.mean_y = static_cast<double>(sum_y) / static_cast<double>(m);
4411
4412 // Compute residuals, TSS, RSS
4413 result.residuals = Array<C>(m, C{});
4414 result.tss = 0.0;
4415 result.rss = 0.0;
4416
4417 for (size_t i = 0; i < m; ++i)
4418 {
4419 C pred = poly.eval(data(i).first);
4420 C res = data(i).second - pred;
4421 result.residuals(i) = res;
4422
4423 const auto dev_y = static_cast<double>(data(i).second) - result.mean_y;
4424 result.tss += dev_y * dev_y;
4425
4426 const auto res_d = static_cast<double>(res);
4427 result.rss += res_d * res_d;
4428 }
4429
4430 result.ess = result.tss - result.rss;
4431
4432 // Compute R²
4433 if (result.tss > 1e-16)
4434 result.r_squared = result.ess / result.tss;
4435 else
4436 result.r_squared = 0.0;
4437
4438 // Compute RMSE
4439 result.rmse = std::sqrt(result.rss / static_cast<double>(m));
4440
4441 return result;
4442}
4443
4444// ===================================================================
4445// Free operators
4446// ===================================================================
4447
4453template <typename C, class M>
4455{
4456 return p * s;
4457}
4458
4464template <typename C, class M>
4466{
4467 return p + s;
4468}
4469
4475template <typename C, class M>
4480
4486template <typename C, class M>
4487std::ostream &operator<<(std::ostream &out, const Gen_MultiPolynomial<C, M> &p)
4488{
4489 return out << p.to_str();
4490}
4491
4492// ===================================================================
4493// Convenient typedefs
4494// ===================================================================
4495
4498
4501
4504
4507
4510
4511} // namespace Aleph
4512
4513#endif // TPL_MULTI_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:522
#define ah_invalid_argument_if(C)
Throws std::invalid_argument if condition holds.
Definition ah-errors.H:639
Parallel functional programming operations using ThreadPool.
High-level sorting functions for Aleph containers.
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
Dynamic heap of elements of type T ordered by a comparison functor.
T getMin()
Remove the minimum element (according to Compare) and return it.
T & insert(const T &item)
Insert a copy of item into the heap.
Dynamic singly linked list with functional programming support.
Definition htlist.H:1155
T & insert(const T &item)
Definition htlist.H:1220
T & append(const T &item)
Definition htlist.H:1271
Generic key-value map implemented on top of a binary search tree.
bool contains(const Key &key) const noexcept
bool is_empty() const noexcept
Sparse multivariate polynomial.
static bool radical_member(const Gen_MultiPolynomial &f, const Array< Gen_MultiPolynomial > &gens)
Test membership in radical of ideal: f ∈ √I.
void divide_scalar_inplace(const Coefficient &s)
Divide every coefficient by s in place.
static Array< Gen_MultiPolynomial > reduced_groebner_basis(const Array< Gen_MultiPolynomial > &generators)
Reduced Gröbner basis (minimized and normalized).
static bool leading_monomials_coprime(const Array< size_t > &a, const Array< size_t > &b, const size_t nvars) noexcept
static Pair_Key canonical_pair(size_t i, size_t j) noexcept
static Gen_MultiPolynomial fit(const Array< std::pair< Array< Coefficient >, Coefficient > > &data, const size_t nvars, const Array< Array< size_t > > &basis)
Basic least-squares polynomial fitting.
static bool _try_lift_primitive_affine_linear_factor_for_main_var(const Gen_MultiPolynomial &p, size_t main_var, const Array< Coefficient > &base_eval_pts, Gen_MultiPolynomial &factor)
Try to lift a primitive affine linear factor from specialized coefficients.
void for_each_term_desc(Op &&op) const
Visit every non-zero term in descending monomial order.
Gen_MultiPolynomial primitive_part() const
Primitive part: polynomial divided by its content.
Gen_MultiPolynomial reduce_modulo(const Array< Gen_MultiPolynomial > &divisors) const
Polynomial reduction modulo an ideal (one-liner).
Gen_MultiPolynomial partial(size_t var, size_t n=1) const
Partial derivative with respect to a variable.
static bool pair_is_recorded(const Pair_Registry &pairs, size_t i, size_t j)
static Gen_MultiPolynomial fit_ridge(const Array< std::pair< Array< Coefficient >, Coefficient > > &data, size_t nvars, const Array< Array< size_t > > &basis, Coefficient *lambda_used=nullptr, double *gcv_score=nullptr)
Regularized least-squares fitting with automatic GCV-based lambda selection.
std::string to_json() const
Serialize polynomial to JSON string.
static bool redundant_pair_by_chain_criterion(const Pair_Candidate &candidate, const Array< Array< size_t > > &leading_monomials, const Pair_Registry &zero_pairs, const size_t nvars)
Gen_MultiPolynomial & operator/=(const Coefficient &s)
In-place scalar division.
static bool contains_ideal(const Array< Gen_MultiPolynomial > &I_gens, const Array< Gen_MultiPolynomial > &J_gens)
Test whether ideal I contains ideal J: I ⊇ J.
static Array< Gen_MultiPolynomial > autoreduced_generators(const Array< Gen_MultiPolynomial > &generators)
static size_t _eval_index_for_var(size_t main_var, size_t var) noexcept
Return the evaluation-array position corresponding to variable var.
size_t degree_in(size_t var) const noexcept
Degree in a specific variable.
Gen_MultiPolynomial operator+(const Gen_MultiPolynomial &q) const
Polynomial addition.
Gen_MultiPolynomial(const size_t nvars, std::initializer_list< std::pair< Array< size_t >, Coefficient > > ts)
Construct from an initializer list of term pairs.
DynMapTree< Pair_Key, bool > Pair_Registry
DynList< FactorTerm > factorize() const
Main multivariate factorization over the integers.
bool is_constant() const noexcept
True if constant or zero (total degree 0).
static Gen_MultiPolynomial _exact_quotient(const Gen_MultiPolynomial &f, const Gen_MultiPolynomial &divisor)
Compute exact quotient f / divisor over Z[x1,...,xn].
static Gen_MultiPolynomial s_poly(const Gen_MultiPolynomial &f, const Gen_MultiPolynomial &g)
S-polynomial (Sylvester polynomial) of two polynomials.
static Pair_Candidate pop_best_pair(Pair_Queue &queue, Pair_Registry &queued_pairs)
static Array< Coefficient > _build_eval_point(size_t nvars, size_t main_var, const Array< size_t > &other_vars, const Array< Coefficient > &coords)
Build the full homomorphic-evaluation point from active-variable coordinates.
Array< Coefficient > eval_batch(const Array< Array< Coefficient > > &pts) const
Evaluate polynomial at multiple points (with parallelism support).
Gen_MultiPolynomial operator-() const
Unary negation.
void to_binary(std::ostream &out) const
Serialize polynomial to binary stream.
Gen_MultiPolynomial operator-(const Gen_MultiPolynomial &q) const
Polynomial subtraction.
void scale_inplace(const Coefficient &s)
Multiply every coefficient by s in place.
std::string to_str(const DynList< std::string > &names=DynList< std::string >()) const
Human-readable string.
static Pair_Candidate make_pair_candidate(size_t i, size_t j, const Array< Array< size_t > > &leading_monomials, size_t nvars)
void for_each_term(Op &&op) const
Visit every non-zero term in ascending monomial order.
static size_t _list_size(const DynList< T > &lst)
Count elements in a DynList.
static bool _try_extract_interpolated_factor(const Gen_MultiPolynomial &p, Gen_MultiPolynomial &factor)
Try to extract an exact non-linear factor via coefficient interpolation.
static bool _try_lift_affine_linear_factor_for_main_var(const Gen_MultiPolynomial &p, size_t main_var, const Array< Coefficient > &base_eval_pts, Gen_MultiPolynomial &factor)
Try to lift a monic affine linear factor for a fixed main variable.
Gen_MultiPolynomial operator/(const Coefficient &s) const
Divide by a scalar.
Gen_MultiPolynomial operator*(const Gen_MultiPolynomial &q) const
Polynomial multiplication.
Array< Coefficient > eval_gradient(const Array< Coefficient > &pt) const
Evaluate gradient at a point.
static void make_monic_inplace(Gen_MultiPolynomial &p)
static bool contains_equal_polynomial(const Array< Gen_MultiPolynomial > &basis, const Gen_MultiPolynomial &p)
std::pair< Array< size_t >, Coefficient > leading_term() const
Leading term (largest monomial in the ordering).
static Gen_MultiPolynomial interpolate(const Array< Array< Coefficient > > &grid, const Array< Coefficient > &values, size_t nvars)
Multivariate interpolation via Newton divided differences (Chung-Yao).
Gen_MultiPolynomial operator+(const Coefficient &s) const
Add a scalar constant term.
Gen_MultiPolynomial operator-(const Coefficient &s) const
Subtract a scalar constant term.
static Gen_MultiPolynomial monomial(size_t nvars, const Array< size_t > &idx, const Coefficient &c=Coefficient(1))
A single monomial .
static Array< Coefficient > _collect_unique_monic_linear_roots(const Gen_Polynomial< Coefficient > &u)
Collect unique roots of monic linear factors from a univariate factorization.
size_t num_terms() const noexcept
Number of non-zero terms.
bool is_zero() const noexcept
True if this is the zero polynomial.
Gen_MultiPolynomial & operator*=(const Coefficient &s)
In-place scalar multiplication.
Coefficient content() const
Content of a multivariate polynomial: GCD of all coefficients.
Gen_MultiPolynomial(const size_t nvars, const DynList< std::pair< Array< size_t >, Coefficient > > &ts)
Construct from a list of (exponent-vector, coefficient) pairs.
static Gen_MultiPolynomial fit_parallel(const Array< std::pair< Array< Coefficient >, Coefficient > > &data, size_t nvars, const Array< Array< size_t > > &basis)
Parallel least-squares fitting.
std::pair< size_t, size_t > Pair_Key
DynBinHeap< Pair_Candidate, Pair_Candidate_Less > Pair_Queue
static DynList< FactorTerm > _factorize_as_univariate_in_var(const Gen_MultiPolynomial &p, size_t active_var)
Factor a polynomial that is effectively univariate in one variable.
Gen_MultiPolynomial & operator*=(const Gen_MultiPolynomial &q)
In-place polynomial multiplication.
static bool _collect_sorted_primitive_factor_groups(const Gen_Polynomial< Coefficient > &u, size_t max_degree, Array< Array< Gen_Polynomial< Coefficient > > > &groups)
Expand, primitive-normalize, and group factors by degree in a stable order.
static bool _search_affine_lift_options(const Gen_MultiPolynomial &p, size_t main_var, const Array< size_t > &other_vars, const Array< Array< Coefficient > > &root_options, const Array< Coefficient > &base_eval_pts, const Coefficient &base_root, size_t pos, Array< Coefficient > &slopes, Gen_MultiPolynomial &factor)
Recursive search for an affine linear multivariate lift.
static Array< Uni_Linear_Factor_Data > _collect_sorted_primitive_linear_factors(const Gen_Polynomial< Coefficient > &u)
Collect primitive linear factors from a univariate factorization in stable order.
Array< Array< Coefficient > > eval_hessian(const Array< Coefficient > &pt) const
Evaluate Hessian at a point.
static Array< Gen_MultiPolynomial > _interpolated_factors_for_main_var(const Gen_MultiPolynomial &p, size_t main_var, Coefficient grid_start, Coefficient grid_step)
Reconstruct primitive factors by interpolation across specialization grids.
static Array< Gen_MultiPolynomial > ideal_power(const Array< Gen_MultiPolynomial > &gens, size_t n)
Power of an ideal: I^n = I · I · ... · I (n times).
Coefficient leading_coeff() const
Leading coefficient.
Gen_MultiPolynomial promote(size_t new_nv) const
Promote to a polynomial with more variables.
void remove_zeros()
Remove all terms whose coefficient is approximately zero.
Array< Gen_MultiPolynomial > gradient() const
Gradient vector: array of all first-order partial derivatives.
static Gen_MultiPolynomial from_json(const std::string &s)
Deserialize from JSON.
Array< size_t > leading_monomial() const
Leading monomial (exponent vector of the leading term).
static bool coeff_is_zero(const Coefficient &c) noexcept
Test whether a coefficient is (approximately) zero.
Gen_Polynomial< Coefficient > homomorphic_eval(size_t keep_var, const Array< Coefficient > &eval_pts) const
Homomorphic evaluation: reduce to univariate by substitution.
Gen_MultiPolynomial & operator-=(const Coefficient &s)
In-place subtraction of a scalar constant term.
static void enqueue_pair_if_needed(Pair_Queue &queue, Pair_Registry &queued_pairs, const Pair_Candidate &candidate, const Array< Array< size_t > > &leading_monomials, Pair_Registry &zero_pairs, const size_t nvars)
static void _append_unique(Array< Coefficient > &vals, const Coefficient &value)
Append value to vals if it is not already present.
static constexpr Coefficient epsilon() noexcept
Machine epsilon for floating-point coefficients.
static DynList< FactorTerm > factor_recombination(Gen_MultiPolynomial f, const Array< Gen_MultiPolynomial > &candidates)
Factor recombination: find true factors from lifted candidates.
static Gen_MultiPolynomial _build_affine_factor_from_linear_data(size_t nvars, size_t main_var, const Array< size_t > &other_vars, const Array< Coefficient > &base_eval_pts, const Coefficient &main_coeff, const Array< Coefficient > &other_coeffs, const Coefficient &base_constant_term)
Build a primitive affine linear factor from specialized coefficients.
static size_t _count_active_vars(const Gen_MultiPolynomial &p, size_t &last_active_var)
Count active variables (positive degree) and remember the last one seen.
static bool _divides_exactly(const Gen_MultiPolynomial &f, const Gen_MultiPolynomial &divisor)
Test whether divisor divides f exactly over Z[x1,...,xn].
static Gen_MultiPolynomial variable(size_t nvars, const size_t var)
The polynomial (a single variable).
static Array< Gen_MultiPolynomial > groebner_basis(const Array< Gen_MultiPolynomial > &generators)
Gröbner basis computation via Buchberger's algorithm.
static Array< Gen_Polynomial< Coefficient > > _expanded_univariate_factors(const Gen_Polynomial< Coefficient > &u)
Expand a univariate factorization into individual factors.
Gen_MultiPolynomial(const size_t nvars, const Coefficient &c=Coefficient{})
Constant polynomial.
static void rebuild_groebner_pair_state(const Array< Gen_MultiPolynomial > &basis, Array< Array< size_t > > &leading_monomials, Pair_Queue &queue, Pair_Registry &queued_pairs, Pair_Registry &zero_pairs)
Gen_MultiPolynomial operator*(const Coefficient &s) const
Multiply by a scalar.
static Gen_MultiPolynomial _substitute_var(const Gen_MultiPolynomial &p, size_t var, const Coefficient &val)
Substitute a single variable with a scalar value.
Gen_MultiPolynomial & operator-=(const Gen_MultiPolynomial &q)
In-place subtraction.
void add_to_coeff(const Array< size_t > &idx, const Coefficient &delta)
Add delta to the coefficient at idx.
static Gen_MultiPolynomial from_binary(std::istream &in)
Deserialize polynomial from binary stream.
static bool _build_interpolation_grid(const Gen_MultiPolynomial &p, const Array< size_t > &other_vars, Coefficient start, Coefficient step, Array< Array< Coefficient > > &grid, size_t &total_points)
Build a regular interpolation grid for the active non-main variables.
Array< size_t > norm(const Array< size_t > &idx) const
Pad an index to nvars_ trailing zeros.
static bool lcm_pair_less(const Pair_Candidate &lhs, const Pair_Candidate &rhs)
Gen_MultiPolynomial()=default
Default constructor: the zero polynomial with 0 variables.
Gen_MultiPolynomial & operator+=(const Coefficient &s)
In-place addition of a scalar constant term.
Coefficient operator()(const Array< Coefficient > &pt) const
Evaluate at a point (function-call syntax).
static Gen_MultiPolynomial _embed_coeff_poly_as_x_term(const Gen_MultiPolynomial &coeff_poly, size_t nvars, size_t main_var, const Array< size_t > &other_vars, size_t exp)
Embed a coefficient polynomial in the ambient ring and multiply by x_main^exp.
static Gen_MultiPolynomial _embed_univariate(const Gen_Polynomial< Coefficient > &u, size_t nv, const size_t x_var)
Embed a univariate polynomial into the multivariate ring.
static bool _try_extract_affine_linear_factor(const Gen_MultiPolynomial &p, Gen_MultiPolynomial &factor)
Try to extract a monic affine linear factor via multivariate lifting.
Coefficient coeff_at(const Array< size_t > &idx) const
Read coefficient at a multi-index (0 if absent).
static Array< Gen_MultiPolynomial > minimize_groebner_basis(Array< Gen_MultiPolynomial > basis)
bool operator==(const Gen_MultiPolynomial &q) const
Polynomial equality.
static Array< Gen_MultiPolynomial > autoreduce_groebner_basis(Array< Gen_MultiPolynomial > basis)
static Array< Coefficient > _decode_grid_point(const Array< Array< Coefficient > > &grid, size_t flat_index)
Decode a flat tensor-grid index into coordinates (last dimension fastest).
size_t num_vars() const noexcept
Number of variables.
static Array< Gen_MultiPolynomial > ideal_sum(const Array< Gen_MultiPolynomial > &a, const Array< Gen_MultiPolynomial > &b)
Sum of two ideals: I + J = ⟨generators(I) ∪ generators(J)⟩.
Gen_MultiPolynomial pow(size_t n) const
Exponentiation by repeated squaring.
bool operator!=(const Gen_MultiPolynomial &q) const
Polynomial inequality.
static Array< Gen_MultiPolynomial > remove_zero_and_duplicates(const Array< Gen_MultiPolynomial > &basis)
Array< Array< Gen_MultiPolynomial > > hessian() const
Hessian matrix: all second-order partial derivatives.
static bool ideal_member(const Gen_MultiPolynomial &f, const Array< Gen_MultiPolynomial > &generators)
Test membership in an ideal via Gröbner basis.
DynMapTree< Array< size_t >, Coefficient, Avl_Tree, MonomOrder > coeffs
std::pair< Array< Gen_MultiPolynomial >, Gen_MultiPolynomial > divmod(const Array< Gen_MultiPolynomial > &divisors) const
Multivariate division with remainder (Buchberger algorithm).
static Gen_MultiPolynomial _build_affine_factor(size_t nvars, size_t main_var, const Array< size_t > &other_vars, const Array< Coefficient > &base_eval_pts, const Array< Coefficient > &slopes, const Coefficient &base_root)
Build a monic affine linear factor from root data.
static bool pair_is_zero_known(const Pair_Registry &zero_pairs, size_t i, size_t j)
Gen_MultiPolynomial & operator+=(const Gen_MultiPolynomial &q)
In-place addition.
Coefficient eval(const Array< Coefficient > &pt) const
Evaluate at a point.
static Coefficient _eval_monomial(const Array< Coefficient > &pt, const Array< size_t > &alpha)
Evaluate a monomial at a point.
static Array< Gen_MultiPolynomial > ideal_product(const Array< Gen_MultiPolynomial > &a, const Array< Gen_MultiPolynomial > &b)
Product of two ideals: I · J = ⟨a_i · b_j : a_i ∈ I, b_j ∈ J⟩.
Gen_MultiPolynomial(const size_t nvars, const Array< size_t > &idx, const Coefficient &c)
Single-term constructor.
size_t degree() const noexcept
Total degree (maximum sum of exponents over all terms).
DynList< std::pair< Array< size_t >, Coefficient > > terms() const
All terms as a list (ascending order).
static Gen_MultiPolynomial fit_weighted(const Array< std::pair< Array< Coefficient >, Coefficient > > &data, size_t nvars, const Array< Array< size_t > > &basis, const Array< Coefficient > &weights)
Weighted least-squares polynomial fitting.
static bool ideals_equal(const Array< Gen_MultiPolynomial > &I_gens, const Array< Gen_MultiPolynomial > &J_gens)
Test equality of two ideals: I = J.
Univariate polynomial over a generic coefficient ring.
void for_each_term(Op &&op) const
Iterate over non-zero terms in ascending exponent order.
void set_coeff(size_t exp, const Coefficient &c)
Set coefficient at exponent; removes entry if zero.
Coefficient get_coeff(size_t exp) const noexcept
Coefficient accessor (read-only at exponent).
size_t size() const noexcept
Count the number of elements of the list.
Definition htlist.H:1065
Graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:428
void for_each(Operation &operation)
Traverse all the container and performs an operation on each element.
Definition ah-dry.H:779
constexpr bool is_empty() const noexcept
Checks if the graph is empty (has no nodes).
Definition graph-dry.H:701
auto get_it() const
Return a properly initialized iterator positioned at the first item on the container.
Definition ah-dry.H:222
constexpr size_t size() const noexcept
Returns the number of entries in the table.
Definition hashDry.H:619
pair< size_t, string > P
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_exp_function > > exp(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4066
__gmp_expr< typename __gmp_resolve_expr< T, V >::value_type, __gmp_binary_expr< __gmp_expr< T, U >, __gmp_expr< V, W >, __gmp_dim_function > > dim(const __gmp_expr< T, U > &expr1, const __gmp_expr< V, W > &expr2)
Definition gmpfrxx.h:4052
__gmp_expr< typename __gmp_resolve_expr< T, V >::value_type, __gmp_binary_expr< __gmp_expr< T, U >, __gmp_expr< V, W >, __gmp_remainder_function > > remainder(const __gmp_expr< T, U > &expr1, const __gmp_expr< V, W > &expr2)
Definition gmpfrxx.h:4115
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_gamma_function > > gamma(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4092
int cmp(const __gmp_expr< T, U > &expr1, const __gmp_expr< V, W > &expr2)
Definition gmpfrxx.h:4118
List_Graph< Graph_Node< string >, Graph_Arc< Empty_Class > > G
DynArray< Graph::Node * > nodes
Definition graphpic.C:406
Singly linked list implementations with head-tail access.
Freq_Node * pred
Predecessor node in level-order traversal.
static mpfr_t y
Definition mpfr_mul_d.c:3
bool divides_index(const Array< size_t > &alpha, const Array< size_t > &beta, size_t nvars) noexcept
Test whether alpha divides beta component-wise (alpha(i) <= beta(i) all i).
Coefficient eval_monomial(const Array< Coefficient > &pt, const Array< size_t > &alpha)
Evaluate monomial at a point (helper for fitting/interpolation).
T falling_factorial(const size_t k, const size_t n)
Falling factorial k(k-1)(k-2)...(k-n+1).
Array< size_t > decrement_index_at(const Array< size_t > &idx, const size_t var, const size_t n=1)
Decrement exponent at a specific variable.
bool is_zero_index(const Array< size_t > &idx) noexcept
Test whether a multi-index is the zero vector.
Array< size_t > flat_to_multi_index(size_t flat, const Array< size_t > &sizes)
Convert flat index to multi-index given sizes per dimension.
T int_power(const T &base, size_t exp)
Integer power by repeated squaring.
Array< size_t > lcm_indices(const Array< size_t > &a, const Array< size_t > &b, const size_t nvars)
Component-wise max = monomial LCM.
size_t multi_to_flat_index(const Array< size_t > &midx, const Array< size_t > &sizes)
Convert multi-index to flat index given sizes per dimension.
size_t total_degree(const Array< size_t > &idx) noexcept
Total degree of a multi-index (sum of exponents).
Array< size_t > add_indices(const Array< size_t > &a, const Array< size_t > &b)
Component-wise addition of two multi-indices.
Array< size_t > sub_indices(const Array< size_t > &beta, const Array< size_t > &alpha, const size_t nvars)
Component-wise subtraction beta - alpha (requires alpha divides beta).
Array< size_t > extend_index(const Array< size_t > &idx, size_t target)
Pad a multi-index with trailing zeros to reach target size.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
Gen_MultiPolynomial< C, M > operator+(const C &s, const Gen_MultiPolynomial< C, M > &p)
Scalar + polynomial (commutative).
auto pmaps(ThreadPool &pool, const Container &c, Op op, size_t chunk_size=0)
Parallel map operation.
bool eq(const C1 &c1, const C2 &c2, Eq e=Eq())
Check equality of two containers using a predicate.
DynList< T > intercept(const Container< T > &c1, const Container< T > &c2)
size_t size(Node *root) noexcept
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.
std::decay_t< typename HeadC::Item_Type > T
Definition ah-zip.H:105
DynArray< T > & in_place_sort(DynArray< T > &c, Cmp cmp=Cmp())
Sorts a DynArray in place.
Definition ahSort.H:321
PolyFitAnalysis< C > analyze_fit(const Gen_MultiPolynomial< C, M > &poly, const Array< std::pair< Array< C >, C > > &data)
Compute residual analysis for a polynomial fit.
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
T product(const Container &container, const T &init=T{1})
Compute product of all elements.
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
T sum(const Container &container, const T &init=T{})
Compute sum of all elements.
STL namespace.
AVL binary search tree with nodes without virtual destructor.
Definition tpl_avl.H:737
Factorization term: a factor polynomial with its multiplicity.
bool operator()(const Pair_Candidate &lhs, const Pair_Candidate &rhs) const
Deterministic ordering of primitive univariate factors by normalized signature.
bool operator()(const Gen_Polynomial< Coefficient > &lhs, const Gen_Polynomial< Coefficient > &rhs) const
Deterministic ordering of primitive univariate linear factors.
bool operator()(const Uni_Linear_Factor_Data &lhs, const Uni_Linear_Factor_Data &rhs) const
Primitive linear factor data for a chosen main variable.
Graded reverse lexicographic monomial order (default).
bool operator()(const Array< size_t > &a, const Array< size_t > &b) const noexcept
Graded lexicographic monomial order.
bool operator()(const Array< size_t > &a, const Array< size_t > &b) const noexcept
Lexicographic monomial order.
bool operator()(const Array< size_t > &a, const Array< size_t > &b) const noexcept
Structure holding residual analysis for polynomial fits.
double rmse
Root mean squared error.
double rss
Residual sum of squares.
double ess
Explained sum of squares.
Array< C > residuals
Per-point residuals.
double r_squared
Coefficient of determination (0–1)
double mean_y
Mean of y values.
double tss
Total sum of squares.
FooMap m(5, fst_unit_pair_hash, snd_unit_pair_hash)
static int * k
gsl_rng * r
Dynamic array container with automatic resizing.
Dynamic binary heap with node-based storage.
Dynamic key-value map based on balanced binary search trees.
Univariate polynomial ring arithmetic over generic coefficients.