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 <cstdint>
49#include <cmath>
50#include <utility>
51#include <iomanip>
52#include <future>
53#include <thread>
54#include <fstream>
55#include <numeric>
56#include <ah-errors.H>
57#include <ahSort.H>
58#include <tpl_array.H>
59#include <tpl_dynBinHeap.H>
60#include <tpl_dynMapTree.H>
61#include <htlist.H>
62#include <ah-parallel.H>
63#include <tpl_polynomial.H>
64
65namespace Aleph {
66
67// ===================================================================
68// Multi-index helpers
69// ===================================================================
70
72namespace multi_poly_detail {
73
74# if defined(_MSC_VER) && !defined(__clang__)
76struct UInt128_Product
77{
78 uint64_t hi = 0;
79 uint64_t lo = 0;
80};
81
84{
85 constexpr uint64_t mask = 0xffffffffULL;
86
87 const uint64_t lhs_lo = lhs & mask;
88 const uint64_t lhs_hi = lhs >> 32;
89 const uint64_t rhs_lo = rhs & mask;
90 const uint64_t rhs_hi = rhs >> 32;
91
92 const uint64_t p0 = lhs_lo * rhs_lo;
93 const uint64_t p1 = lhs_lo * rhs_hi;
94 const uint64_t p2 = lhs_hi * rhs_lo;
95 const uint64_t p3 = lhs_hi * rhs_hi;
96
97 const uint64_t middle = (p0 >> 32) + (p1 & mask) + (p2 & mask);
98 return UInt128_Product{p3 + (p1 >> 32) + (p2 >> 32) + (middle >> 32),
99 (p0 & mask) | (middle << 32)};
100}
101
104 UInt128_Product rhs) noexcept
105{
106 if (lhs.hi < rhs.hi)
107 return -1;
108 if (rhs.hi < lhs.hi)
109 return 1;
110 if (lhs.lo < rhs.lo)
111 return -1;
112 if (rhs.lo < lhs.lo)
113 return 1;
114 return 0;
115}
116
118template <typename T>
120{
121 using U = std::make_unsigned_t<T>;
122 U magnitude = static_cast<U>(value);
123 if constexpr (std::is_signed_v<T>)
124 if (value < 0)
125 magnitude = U(0) - magnitude;
126 return static_cast<uint64_t>(magnitude);
127}
128
130template <typename T>
131[[nodiscard]] bool product_is_negative(T lhs, T rhs, UInt128_Product magnitude) noexcept
132{
133 if (magnitude.hi == 0 and magnitude.lo == 0)
134 return false;
135 if constexpr (std::is_signed_v<T>)
136 return (lhs < 0) != (rhs < 0);
137 else
138 return false;
139}
140
142template <typename T>
144{
145 static_assert(std::is_integral_v<T>, "Integral product comparison requires integral operands");
146 static_assert(sizeof(T) <= sizeof(uint64_t),
147 "MSVC product comparison supports built-in integral types up to 64 bits");
148
153
156
158 return lhs_negative ? -1 : 1;
159
162}
163# endif
164
166inline size_t total_degree(const Array<size_t> &idx) noexcept
167{
168 size_t sum = 0;
169 for (size_t i = 0; i < idx.size(); ++i)
170 sum += idx(i);
171 return sum;
172}
173
175inline bool is_zero_index(const Array<size_t> &idx) noexcept
176{
177 for (size_t i = 0; i < idx.size(); ++i)
178 if (idx(i) != 0)
179 return false;
180 return true;
181}
182
187{
188 const size_t n = std::max(a.size(), b.size());
189 Array<size_t> r(n, size_t{0});
190 for (size_t i = 0; i < a.size(); ++i)
191 r(i) += a(i);
192 for (size_t i = 0; i < b.size(); ++i)
193 r(i) += b(i);
194 return r;
195}
196
198inline Array<size_t> extend_index(const Array<size_t> &idx, size_t target)
199{
200 if (idx.size() >= target)
201 return idx;
202 Array<size_t> r(target, size_t{0});
203 for (size_t i = 0; i < idx.size(); ++i)
204 r(i) = idx(i);
205 return r;
206}
207
209template <typename T>
210T int_power(const T &base, size_t exp)
211{
212 T result = T(1);
213 T b = base;
214 while (exp > 0)
215 {
216 if (exp & 1)
217 result *= b;
218 b *= b;
219 exp >>= 1;
220 }
221 return result;
222}
223
224// ===================================================================
225// Phase 2 — Differential calculus, interpolation, serialization, parallelism
226// ===================================================================
227
229inline Array<size_t> decrement_index_at(const Array<size_t> &idx, const size_t var, const size_t n = 1)
230{
231 Array<size_t> r = idx;
232 r(var) -= n;
233 return r;
234}
235
239template <typename T>
240T falling_factorial(const size_t k, const size_t n)
241{
242 if (n == 0)
243 return T(1);
244 if (k < n)
245 return T(0);
246 T res = T(1);
247 for (size_t i = 0; i < n; ++i)
248 res *= T(k - i);
249 return res;
250}
251
256{
257 const size_t n = sizes.size();
258 Array<size_t> r(n, size_t{0});
259 for (size_t i = n; i > 0; --i)
260 {
261 r(i - 1) = flat % sizes(i - 1);
262 flat /= sizes(i - 1);
263 }
264 return r;
265}
266
268inline size_t multi_to_flat_index(const Array<size_t> &midx, const Array<size_t> &sizes)
269{
270 size_t flat = 0, stride = 1;
271 const size_t n = sizes.size();
272 for (size_t i = n; i > 0; --i)
273 {
274 flat += midx(i - 1) * stride;
275 stride *= sizes(i - 1);
276 }
277 return flat;
278}
279
281template <typename Coefficient>
283{
285 for (size_t j = 0; j < alpha.size(); ++j)
286 if (alpha(j) != 0)
287 v *= int_power(pt(j), alpha(j));
288 return v;
289}
290
292inline bool divides_index(const Array<size_t> &alpha, const Array<size_t> &beta, size_t nvars) noexcept
293{
294 for (size_t i = 0; i < nvars; ++i)
295 {
296 const size_t ai = i < alpha.size() ? alpha(i) : 0;
297 const size_t bi = i < beta.size() ? beta(i) : 0;
298 if (ai > bi)
299 return false;
300 }
301 return true;
302}
303
305inline Array<size_t> lcm_indices(const Array<size_t> &a, const Array<size_t> &b, const size_t nvars)
306{
307 Array<size_t> r(nvars, size_t{0});
308 for (size_t i = 0; i < nvars; ++i)
309 r(i) = std::max(i < a.size() ? a(i) : 0, i < b.size() ? b(i) : 0);
310 return r;
311}
312
315 const Array<size_t> &alpha,
316 const size_t nvars)
317{
318 Array<size_t> r(nvars, size_t{0});
319 for (size_t i = 0; i < nvars; ++i)
320 r(i) = (i < beta.size() ? beta(i) : 0) - (i < alpha.size() ? alpha(i) : 0);
321 return r;
322}
323
324} // namespace multi_poly_detail
325
326// ===================================================================
327// Monomial orderings
328// ===================================================================
329
337{
338 bool operator()(const Array<size_t> &a, const Array<size_t> &b) const noexcept
339 {
340 const size_t n = std::max(a.size(), b.size());
341 for (size_t i = 0; i < n; ++i)
342 {
343 const size_t ai = i < a.size() ? a(i) : 0;
344 const size_t bi = i < b.size() ? b(i) : 0;
345 if (ai < bi)
346 return true;
347 if (ai > bi)
348 return false;
349 }
350 return false;
351 }
352};
353
359{
360 bool operator()(const Array<size_t> &a, const Array<size_t> &b) const noexcept
361 {
362 const size_t da = multi_poly_detail::total_degree(a);
363 const size_t db = multi_poly_detail::total_degree(b);
364 if (da != db)
365 return da < db;
366 return Lex_Order()(a, b);
367 }
368};
369
377{
378 bool operator()(const Array<size_t> &a, const Array<size_t> &b) const noexcept
379 {
380 const size_t da = multi_poly_detail::total_degree(a);
381 const size_t db = multi_poly_detail::total_degree(b);
382 if (da != db)
383 return da < db;
384 const size_t n = std::max(a.size(), b.size());
385 for (size_t i = n; i > 0; --i)
386 {
387 const size_t ai = i - 1 < a.size() ? a(i - 1) : 0;
388 const size_t bi = i - 1 < b.size() ? b(i - 1) : 0;
389 if (ai > bi)
390 return true; // larger last exp → smaller in grevlex
391 if (ai < bi)
392 return false;
393 }
394 return false;
395 }
396};
397
398// ===================================================================
399// Gen_MultiPolynomial
400// ===================================================================
401
413template <typename Coefficient = double, class MonomOrder = Grevlex_Order>
415{
416 size_t nvars_ = 0;
417
419
422 {
424 }
425
427 requires(not std::is_integral_v<Coefficient>)
428 {
429 if (p.is_zero())
430 return;
431
432 Coefficient lc = p.leading_coeff();
434 p /= lc;
435 }
436
438 const Gen_MultiPolynomial &p)
439 {
440 for (size_t i = 0; i < basis.size(); ++i)
441 if (basis(i) == p)
442 return true;
443 return false;
444 }
445
447 const Array<size_t> &b,
448 const size_t nvars) noexcept
449 {
450 for (size_t v = 0; v < nvars; ++v)
451 {
452 const size_t av = v < a.size() ? a(v) : 0;
453 const size_t bv = v < b.size() ? b(v) : 0;
454 if (std::min(av, bv) != 0)
455 return false;
456 }
457 return true;
458 }
459
460 using Pair_Key = std::pair<size_t, size_t>;
462
463 [[nodiscard]] static Pair_Key canonical_pair(size_t i, size_t j) noexcept
464 {
465 return i < j ? std::make_pair(i, j) : std::make_pair(j, i);
466 }
467
469 {
470 size_t i = 0;
471 size_t j = 0;
473 size_t lcm_degree = 0;
474 };
475
476 [[nodiscard]] static bool lcm_pair_less(const Pair_Candidate &lhs, const Pair_Candidate &rhs)
477 {
478 if (lhs.lcm_degree != rhs.lcm_degree)
479 return lhs.lcm_degree < rhs.lcm_degree;
480
481 MonomOrder order;
482 if (order(lhs.lcm, rhs.lcm))
483 return true;
484 if (order(rhs.lcm, lhs.lcm))
485 return false;
486
487 return canonical_pair(lhs.i, lhs.j) < canonical_pair(rhs.i, rhs.j);
488 }
489
491 {
492 [[nodiscard]] bool operator()(const Pair_Candidate &lhs, const Pair_Candidate &rhs) const
493 {
494 return lcm_pair_less(lhs, rhs);
495 }
496 };
497
499
500 [[nodiscard]] static bool pair_is_recorded(const Pair_Registry &pairs, size_t i, size_t j)
501 {
502 return pairs.contains(canonical_pair(i, j));
503 }
504
505 [[nodiscard]] static bool pair_is_zero_known(const Pair_Registry &zero_pairs, size_t i, size_t j)
506 {
507 return pair_is_recorded(zero_pairs, i, j);
508 }
509
511 size_t i, size_t j, const Array<Array<size_t>> &leading_monomials, size_t nvars)
512 {
513 const auto [a, b] = canonical_pair(i, j);
515 candidate.i = a;
516 candidate.j = b;
519 return candidate;
520 }
521
525 const size_t nvars)
526 {
527 for (size_t k = 0; k < leading_monomials.size(); ++k)
528 {
529 if (k == candidate.i or k == candidate.j)
530 continue;
531
533 continue;
534
537 return true;
538 }
539
540 return false;
541 }
542
544 {
545 ah_domain_error_if(queue.is_empty()) << "pop_best_pair: empty pair queue";
546
547 Pair_Candidate best = queue.getMin();
548 queued_pairs.remove_key(canonical_pair(best.i, best.j));
549 return best;
550 }
551
557 const size_t nvars)
558 {
560 return;
561
563 {
564 zero_pairs.insert(canonical_pair(candidate.i, candidate.j), true);
565 return;
566 }
567
569 {
570 zero_pairs.insert(canonical_pair(candidate.i, candidate.j), true);
571 return;
572 }
573
574 queue.insert(candidate);
576 }
577
580 requires(not std::is_integral_v<Coefficient>)
581 {
583
584 for (size_t i = 0; i < generators.size(); ++i)
585 {
588
589 if (reduced.is_empty())
590 {
591 reduced.append(std::move(g));
592 continue;
593 }
594
596 if (r.is_zero())
597 continue;
598
601 reduced.append(std::move(r));
602 }
603
604 return reduced;
605 }
606
608 requires(not std::is_integral_v<Coefficient>)
609 {
610 bool changed = true;
611 while (changed)
612 {
613 changed = false;
614 size_t idx_to_remove = basis.size();
615
616 for (size_t i = 0; i < basis.size(); ++i)
617 {
618 if (idx_to_remove < basis.size())
619 break;
620
621 Array<size_t> lm_i = basis(i).leading_monomial();
622
623 for (size_t j = 0; j < basis.size(); ++j)
624 {
625 if (i == j)
626 continue;
627
630 {
631 idx_to_remove = i;
632 break;
633 }
634 }
635 }
636
637 if (idx_to_remove < basis.size())
638 {
640 for (size_t i = 0; i < basis.size(); ++i)
641 if (i != idx_to_remove)
642 compact.append(std::move(basis(i)));
643 basis = std::move(compact);
644 changed = true;
645 }
646 }
647
648 return basis;
649 }
650
653 {
655 for (size_t i = 0; i < basis.size(); ++i)
656 {
657 if (basis(i).is_zero())
658 continue;
660 continue;
661 compact.append(basis(i));
662 }
663 return compact;
664 }
665
668 requires(not std::is_integral_v<Coefficient>)
669 {
670 bool changed = true;
671 while (changed)
672 {
673 changed = false;
674 for (size_t i = 0; i < basis.size(); ++i)
675 {
676 if (basis(i).is_zero())
677 {
679 for (size_t j = 0; j < basis.size(); ++j)
680 if (j != i)
681 compact.append(basis(j));
682 basis = std::move(compact);
683 changed = true;
684 break;
685 }
686
688 for (size_t j = 0; j < basis.size(); ++j)
689 if (j != i and not basis(j).is_zero())
690 others.append(basis(j));
691
692 if (others.is_empty())
693 {
695 continue;
696 }
697
699
700 if (r.is_zero())
701 {
703 for (size_t j = 0; j < basis.size(); ++j)
704 if (j != i)
705 compact.append(basis(j));
706 basis = std::move(compact);
707 changed = true;
708 break;
709 }
710
713 {
715 for (size_t j = 0; j < basis.size(); ++j)
716 if (j != i)
717 compact.append(basis(j));
718 basis = std::move(compact);
719 changed = true;
720 break;
721 }
722
723 if (r != basis(i))
724 {
725 basis(i) = std::move(r);
726 changed = true;
727 break;
728 }
729 }
730 }
731
733 for (size_t i = 0; i < basis.size(); ++i)
735 return basis;
736 }
737
740 Pair_Queue &queue,
743 requires(not std::is_integral_v<Coefficient>)
744 {
746 for (size_t i = 0; i < basis.size(); ++i)
747 leading_monomials.append(basis(i).leading_monomial());
748
749 queue = Pair_Queue();
752
753 if (basis.size() < 2)
754 return;
755
756 for (size_t i = 0; i < basis.size(); ++i)
757 for (size_t j = i + 1; j < basis.size(); ++j)
763 basis(0).nvars_);
764 }
765
766public:
767 // =================================================================
768 // Type aliases
769 // =================================================================
770
774
775 // =================================================================
776 // Coefficient zero-testing
777 // =================================================================
778
782 static constexpr Coefficient epsilon() noexcept
783 {
784 if constexpr (std::is_floating_point_v<Coefficient>)
785 return Coefficient(128) * std::numeric_limits<Coefficient>::epsilon();
786 else
787 return Coefficient{};
788 }
789
794 static bool coeff_is_zero(const Coefficient &c) noexcept
795 {
796 if constexpr (std::is_floating_point_v<Coefficient>)
797 return std::abs(c) <= epsilon();
798 else
799 return c == Coefficient{};
800 }
801
802 // =================================================================
803 // Constructors
804 // =================================================================
805
808
813 explicit Gen_MultiPolynomial(const size_t nvars, const Coefficient &c = Coefficient{})
814 : nvars_(nvars)
815 {
816 if (not coeff_is_zero(c))
817 coeffs.insert(Array<size_t>(nvars_, size_t{0}), c);
818 }
819
825 Gen_MultiPolynomial(const size_t nvars, const Array<size_t> &idx, const Coefficient &c)
826 : nvars_(nvars)
827 {
828 if (not coeff_is_zero(c))
829 coeffs.insert(norm(idx), c);
830 }
831
836 Gen_MultiPolynomial(const size_t nvars, const DynList<std::pair<Array<size_t>, Coefficient>> &ts)
837 : nvars_(nvars)
838 {
839 ts.for_each([this](const std::pair<Array<size_t>, Coefficient> &t)
840 {
841 add_to_coeff(t.first, t.second);
842 });
843 }
844
850 std::initializer_list<std::pair<Array<size_t>, Coefficient>> ts)
851 : nvars_(nvars)
852 {
853 for (const auto &t : ts)
854 add_to_coeff(t.first, t.second);
855 }
856
857 // =================================================================
858 // Factory helpers
859 // =================================================================
860
866 [[nodiscard]] static Gen_MultiPolynomial variable(size_t nvars, const size_t var)
867 {
868 ah_domain_error_if(var >= nvars) << "variable index " << var << " >= nvars " << nvars;
869 Array<size_t> idx(nvars, size_t{0});
870 idx(var) = 1;
871 return Gen_MultiPolynomial(nvars, idx, Coefficient(1));
872 }
873
881 const Array<size_t> &idx,
882 const Coefficient &c = Coefficient(1))
883 {
884 return Gen_MultiPolynomial(nvars, idx, c);
885 }
886
887 // =================================================================
888 // Properties
889 // =================================================================
890
893 {
894 return coeffs.is_empty();
895 }
896
899 {
900 if (coeffs.is_empty())
901 return true;
902 if (coeffs.size() != 1)
903 return false;
904 return multi_poly_detail::is_zero_index(coeffs.min().first);
905 }
906
909 {
910 return nvars_;
911 }
912
915 {
916 return coeffs.size();
917 }
918
923 {
924 size_t d = 0;
925 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
926 if (size_t td = multi_poly_detail::total_degree(it.get_curr().first); td > d)
927 d = td;
928 return d;
929 }
930
935 [[nodiscard]] size_t degree_in(size_t var) const noexcept
936 {
937 size_t d = 0;
938 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
939 {
940 const auto &idx = it.get_curr().first;
941 if (const size_t e = var < idx.size() ? idx(var) : 0; e > d)
942 d = e;
943 }
944 return d;
945 }
946
947 // =================================================================
948 // Leading-term access
949 // =================================================================
950
955 [[nodiscard]] std::pair<Array<size_t>, Coefficient> leading_term() const
956 {
957 ah_domain_error_if(is_zero()) << "leading_term of zero polynomial";
958 const auto &m = coeffs.max();
959 return {m.first, m.second};
960 }
961
967 {
968 return leading_term().second;
969 }
970
976 {
977 return leading_term().first;
978 }
979
980 // =================================================================
981 // Coefficient access
982 // =================================================================
983
989 {
990 auto *p = coeffs.search(norm(idx));
991 return p ? p->second : Coefficient{};
992 }
993
994 // =================================================================
995 // Iteration
996 // =================================================================
997
1001 template <class Op>
1002 void for_each_term(Op &&op) const
1003 {
1004 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1005 {
1006 const auto &p = it.get_curr();
1007 op(p.first, p.second);
1008 }
1009 }
1010
1014 template <class Op>
1015 void for_each_term_desc(Op &&op) const
1016 {
1018 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1019 {
1020 const auto &p = it.get_curr();
1021 tmp.insert({p.first, p.second}); // prepend → reversed
1022 }
1023 tmp.for_each([&op](const std::pair<Array<size_t>, Coefficient> &t)
1024 {
1025 op(t.first, t.second);
1026 });
1027 }
1028
1033 {
1035 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
1036 {
1037 r.append({idx, c});
1038 });
1039 return r;
1040 }
1041
1042 // =================================================================
1043 // Coefficient modification
1044 // =================================================================
1045
1053 void add_to_coeff(const Array<size_t> &idx, const Coefficient &delta)
1054 {
1055 if (coeff_is_zero(delta))
1056 return;
1057
1058 auto nidx = norm(idx);
1059 auto *p = coeffs.search(nidx);
1060 if (p != nullptr)
1061 {
1062 p->second = p->second + delta;
1063 if (coeff_is_zero(p->second))
1064 coeffs.remove_key(nidx);
1065 }
1066 else
1067 coeffs.insert(nidx, delta);
1068 }
1069
1072 {
1074 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1075 {
1076 auto &p = it.get_curr();
1077 if (coeff_is_zero(p.second))
1078 zs.append(p.first);
1079 }
1080 zs.for_each([this](const Array<size_t> &k)
1081 {
1082 coeffs.remove_key(k);
1083 });
1084 }
1085
1090 {
1091 if (coeffs.is_empty())
1092 return;
1093
1094 if (coeff_is_zero(s))
1095 {
1096 coeffs = decltype(coeffs)();
1097 return;
1098 }
1099
1100 if (s == Coefficient(1))
1101 return;
1102
1103 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1104 it.get_curr().second *= s;
1105
1106 remove_zeros();
1107 }
1108
1114 {
1115 ah_domain_error_if(coeff_is_zero(s)) << "multivariate polynomial division by zero scalar";
1116
1117 if (s == Coefficient(1))
1118 return;
1119
1120 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
1121 it.get_curr().second /= s;
1122
1123 remove_zeros();
1124 }
1125
1126 // =================================================================
1127 // Arithmetic — polynomial ± polynomial
1128 // =================================================================
1129
1135 {
1136 Gen_MultiPolynomial r = *this;
1137 r += q;
1138 return r;
1139 }
1140
1146 {
1147 if (this == &q)
1148 {
1150 return *this;
1151 }
1152
1153 nvars_ = std::max(nvars_, q.nvars_);
1154 q.for_each_term([this](const Array<size_t> &idx, const Coefficient &c)
1155 {
1156 add_to_coeff(idx, c);
1157 });
1158 return *this;
1159 }
1160
1166 {
1167 Gen_MultiPolynomial r = *this;
1168 r += s;
1169 return r;
1170 }
1171
1177 {
1178 add_to_coeff(Array<size_t>(nvars_, size_t{0}), s);
1179 return *this;
1180 }
1181
1187 {
1188 Gen_MultiPolynomial r = *this;
1189 r -= q;
1190 return r;
1191 }
1192
1198 {
1199 if (this == &q)
1200 {
1201 coeffs = decltype(coeffs)();
1202 return *this;
1203 }
1204
1205 nvars_ = std::max(nvars_, q.nvars_);
1206 q.for_each_term([this](const Array<size_t> &idx, const Coefficient &c)
1207 {
1208 add_to_coeff(idx, -c);
1209 });
1210 return *this;
1211 }
1212
1218 {
1219 Gen_MultiPolynomial r = *this;
1220 r -= s;
1221 return r;
1222 }
1223
1229 {
1230 add_to_coeff(Array<size_t>(nvars_, size_t{0}), -s);
1231 return *this;
1232 }
1233
1238 {
1240 r.nvars_ = nvars_;
1241 for_each_term([&r](const Array<size_t> &idx, const Coefficient &c)
1242 {
1243 r.coeffs.insert(idx, -c);
1244 });
1245 return r;
1246 }
1247
1248 // =================================================================
1249 // Arithmetic — polynomial * polynomial
1250 // =================================================================
1251
1260 {
1261 const size_t nv = std::max(nvars_, q.nvars_);
1262
1263 if (is_zero() or q.is_zero())
1264 return Gen_MultiPolynomial(nv);
1265
1266 const Gen_MultiPolynomial *outer = this;
1267 const Gen_MultiPolynomial *inner = &q;
1268 if (q.num_terms() < num_terms())
1269 {
1270 outer = &q;
1271 inner = this;
1272 }
1273
1275 r.nvars_ = nv;
1276
1277 outer->for_each_term([&](const Array<size_t> &ai, const Coefficient &ac)
1278 {
1279 inner->for_each_term([&](const Array<size_t> &bi, const Coefficient &bc)
1280 {
1283 r.add_to_coeff(si, ac * bc);
1284 });
1285 });
1286
1287 return r;
1288 }
1289
1295 {
1296 *this = *this * q;
1297 return *this;
1298 }
1299
1300 // =================================================================
1301 // Arithmetic — scalar operations
1302 // =================================================================
1303
1309 {
1310 if (coeff_is_zero(s))
1312
1314 r.nvars_ = nvars_;
1315 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
1316 {
1317 if (Coefficient prod = c * s; not coeff_is_zero(prod))
1318 r.coeffs.insert(idx, prod);
1319 });
1320 return r;
1321 }
1322
1328 {
1329 scale_inplace(s);
1330 return *this;
1331 }
1332
1339 {
1340 ah_domain_error_if(coeff_is_zero(s)) << "multivariate polynomial division by zero scalar";
1341
1343 r.nvars_ = nvars_;
1344 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
1345 {
1346 Coefficient q = c / s;
1347 if (not coeff_is_zero(q))
1348 r.coeffs.insert(idx, q);
1349 });
1350 return r;
1351 }
1352
1359 {
1361 return *this;
1362 }
1363
1364 // =================================================================
1365 // Exponentiation
1366 // =================================================================
1367
1373 {
1375 Gen_MultiPolynomial base = *this;
1376 while (n > 0)
1377 {
1378 if (n & 1)
1379 result *= base;
1380 base *= base;
1381 n >>= 1;
1382 }
1383 return result;
1384 }
1385
1386 // =================================================================
1387 // Evaluation
1388 // =================================================================
1389
1396 {
1398 << "eval: point has " << pt.size() << " components but polynomial has " << nvars_
1399 << " variables";
1400
1401 Coefficient result = Coefficient{};
1402
1403 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
1404 {
1405 Coefficient term = c;
1406 for (size_t j = 0; j < idx.size(); ++j)
1407 {
1408 if (idx(j) == 0)
1409 continue;
1410 term *= multi_poly_detail::int_power(pt(j), idx(j));
1411 }
1412 result += term;
1413 });
1414
1415 return result;
1416 }
1417
1424 {
1425 return eval(pt);
1426 }
1427
1428 // =================================================================
1429 // Comparison
1430 // =================================================================
1431
1437 {
1438 if (num_terms() != q.num_terms())
1439 return false;
1440
1441 bool eq = true;
1442 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
1443 {
1444 if (not eq)
1445 return;
1446 if (not coeff_is_zero(c - q.coeff_at(idx)))
1447 eq = false;
1448 });
1449 return eq;
1450 }
1451
1454 {
1455 return not(*this == q);
1456 }
1457
1458 // =================================================================
1459 // String representation
1460 // =================================================================
1461
1470 [[nodiscard]] std::string to_str(const DynList<std::string> &names = DynList<std::string>()) const
1471 {
1472 if (is_zero())
1473 return "0";
1474
1475 // Build name lookup
1477 {
1478 size_t j = 0;
1479 names.for_each([&](const std::string &n)
1480 {
1481 if (j < nvars_)
1482 vn.append(n);
1483 ++j;
1484 });
1485 }
1486 while (vn.size() < nvars_)
1487 if (nvars_ <= 3)
1488 {
1489 constexpr char abc[] = {'x', 'y', 'z'};
1490 vn.append(std::string(1, abc[vn.size()]));
1491 }
1492 else
1493 vn.append("x" + std::to_string(vn.size()));
1494
1495 std::ostringstream oss;
1496 bool first = true;
1497
1498 for_each_term_desc([&](const Array<size_t> &idx, const Coefficient &c)
1499 {
1500 Coefficient ac = c;
1501 bool neg = false;
1502 if constexpr (std::is_floating_point_v<Coefficient>)
1503 {
1504 if (c < 0)
1505 {
1506 ac = -c;
1507 neg = true;
1508 }
1509 }
1510 else
1511 {
1512 if (c < Coefficient{})
1513 {
1514 ac = -c;
1515 neg = true;
1516 }
1517 }
1518
1520
1521 // Sign
1522 if (first)
1523 {
1524 if (neg)
1525 oss << "-";
1526 }
1527 else
1528 oss << (neg ? " - " : " + ");
1529
1530 // Coefficient (suppress 1 / -1 when variables are present)
1531 bool cp = false;
1533 {
1534 oss << ac;
1535 cp = true;
1536 }
1537
1538 // Variables
1539 if (has_vars)
1540 {
1541 bool fv = true;
1542 for (size_t j = 0; j < idx.size(); ++j)
1543 {
1544 if (idx(j) == 0)
1545 continue;
1546 if (cp or not fv)
1547 oss << "*";
1548 oss << vn(j);
1549 if (idx(j) > 1)
1550 oss << "^" << idx(j);
1551 fv = false;
1552 }
1553 }
1554
1555 first = false;
1556 });
1557
1558 return oss.str();
1559 }
1560
1561 // =================================================================
1562 // Promote
1563 // =================================================================
1564
1574 {
1576 << "cannot demote from " << nvars_ << " to " << new_nv << " variables";
1577
1578 if (new_nv == nvars_)
1579 return *this;
1580
1582 r.nvars_ = new_nv;
1583 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
1584 {
1585 r.coeffs.insert(multi_poly_detail::extend_index(idx, new_nv), c);
1586 });
1587 return r;
1588 }
1589
1590 // =================================================================
1591 // Least-Squares Fitting (Layer 2 - Industrial)
1592 // =================================================================
1593
1609 const Array<std::pair<Array<Coefficient>, Coefficient>> &data,
1610 const size_t nvars,
1611 const Array<Array<size_t>> &basis)
1612 {
1613 const size_t m = data.size();
1614 const size_t b = basis.size();
1615
1616 ah_domain_error_if(m == 0) << "fit: data is empty";
1617 ah_domain_error_if(b == 0) << "fit: basis is empty";
1618
1619 // Build design matrix A [m x b]
1622
1623 for (size_t i = 0; i < m; ++i)
1624 {
1625 for (size_t j = 0; j < b; ++j)
1626 A(i)(j) = _eval_monomial(data(i).first, basis(j));
1627 y(i) = data(i).second;
1628 }
1629
1630 // Normal equations: (A^T A) c = A^T y
1633
1634 for (size_t i = 0; i < b; ++i)
1635 {
1636 for (size_t j = 0; j < b; ++j)
1637 {
1638 AtA(i)(j) = Coefficient{};
1639 for (size_t k = 0; k < m; ++k)
1640 AtA(i)(j) += A(k)(i) * A(k)(j);
1641 }
1642
1643 Aty(i) = Coefficient{};
1644 for (size_t k = 0; k < m; ++k)
1645 Aty(i) += A(k)(i) * y(k);
1646 }
1647
1648 // Solve via Gaussian elimination with pivoting
1649 Array<Coefficient> c(b);
1650 for (size_t col = 0; col < b; ++col)
1651 {
1652 // Partial pivoting
1653 size_t prow = col;
1654 Coefficient pval = std::abs(AtA(col)(col));
1655 for (size_t row = col + 1; row < b; ++row)
1656 {
1657 Coefficient av = std::abs(AtA(row)(col));
1658 if (av > pval)
1659 {
1660 pval = av;
1661 prow = row;
1662 }
1663 }
1664
1665 ah_domain_error_if(coeff_is_zero(AtA(prow)(col))) << "fit: singular system at column " << col;
1666
1667 if (prow != col)
1668 {
1669 std::swap(AtA(col), AtA(prow));
1670 std::swap(Aty(col), Aty(prow));
1671 }
1672
1673 // Eliminate
1674 for (size_t row = col + 1; row < b; ++row)
1675 {
1676 Coefficient f = AtA(row)(col) / AtA(col)(col);
1677 for (size_t j = col; j < b; ++j)
1678 AtA(row)(j) -= f * AtA(col)(j);
1679 Aty(row) -= f * Aty(col);
1680 }
1681 }
1682
1683 // Back-substitution
1684 for (size_t i = b; i > 0; --i)
1685 {
1686 c(i - 1) = Aty(i - 1);
1687 for (size_t j = i; j < b; ++j)
1688 c(i - 1) -= AtA(i - 1)(j) * c(j);
1689 c(i - 1) /= AtA(i - 1)(i - 1);
1690 }
1691
1692 // Assemble polynomial
1693 Gen_MultiPolynomial result(nvars);
1694 for (size_t j = 0; j < b; ++j)
1695 {
1696 if (not coeff_is_zero(c(j)))
1697 result.add_to_coeff(basis(j), c(j));
1698 }
1699
1700 return result;
1701 }
1702
1721 const Array<std::pair<Array<Coefficient>, Coefficient>> &data,
1722 size_t nvars,
1723 const Array<Array<size_t>> &basis,
1724 const Array<Coefficient> &weights)
1725 {
1726 const size_t m = data.size();
1727 const size_t b = basis.size();
1728
1729 ah_domain_error_if(m == 0) << "fit_weighted: data is empty";
1730 ah_domain_error_if(b == 0) << "fit_weighted: basis is empty";
1731 ah_domain_error_if(weights.size() != m)
1732 << "fit_weighted: weights.size() " << weights.size() << " != data.size() " << m;
1733
1734 // Build weighted design matrix: A_ij = sqrt(w_i) * basis_j(x_i)
1737
1738 for (size_t i = 0; i < m; ++i)
1739 {
1740 const Coefficient sqrt_w = std::sqrt(weights(i));
1741 for (size_t j = 0; j < b; ++j)
1742 A(i)(j) = sqrt_w * _eval_monomial(data(i).first, basis(j));
1743 y_w(i) = sqrt_w * data(i).second;
1744 }
1745
1746 // Normal equations: (A^T A) c = A^T y
1749
1750 for (size_t i = 0; i < b; ++i)
1751 {
1752 for (size_t j = 0; j < b; ++j)
1753 {
1754 AtA(i)(j) = Coefficient{};
1755 for (size_t k = 0; k < m; ++k)
1756 AtA(i)(j) += A(k)(i) * A(k)(j);
1757 }
1758
1759 Aty(i) = Coefficient{};
1760 for (size_t k = 0; k < m; ++k)
1761 Aty(i) += A(k)(i) * y_w(k);
1762 }
1763
1764 // Solve via Gaussian elimination
1765 Array<Coefficient> c(b);
1766 for (size_t col = 0; col < b; ++col)
1767 {
1768 // Partial pivoting
1769 size_t prow = col;
1770 Coefficient pval = std::abs(AtA(col)(col));
1771 for (size_t row = col + 1; row < b; ++row)
1772 {
1773 Coefficient av = std::abs(AtA(row)(col));
1774 if (av > pval)
1775 {
1776 pval = av;
1777 prow = row;
1778 }
1779 }
1780
1782 << "fit_weighted: singular system at column " << col;
1783
1784 if (prow != col)
1785 {
1786 std::swap(AtA(col), AtA(prow));
1787 std::swap(Aty(col), Aty(prow));
1788 }
1789
1790 // Eliminate
1791 for (size_t row = col + 1; row < b; ++row)
1792 {
1793 Coefficient f = AtA(row)(col) / AtA(col)(col);
1794 for (size_t j = col; j < b; ++j)
1795 AtA(row)(j) -= f * AtA(col)(j);
1796 Aty(row) -= f * Aty(col);
1797 }
1798 }
1799
1800 // Back-substitution
1801 for (size_t i = b; i > 0; --i)
1802 {
1803 c(i - 1) = Aty(i - 1);
1804 for (size_t j = i; j < b; ++j)
1805 c(i - 1) -= AtA(i - 1)(j) * c(j);
1806 c(i - 1) /= AtA(i - 1)(i - 1);
1807 }
1808
1809 // Assemble polynomial
1810 Gen_MultiPolynomial result(nvars);
1811 for (size_t j = 0; j < b; ++j)
1812 if (not coeff_is_zero(c(j)))
1813 result.add_to_coeff(basis(j), c(j));
1814
1815 return result;
1816 }
1817
1842 const Array<std::pair<Array<Coefficient>, Coefficient>> &data,
1843 size_t nvars,
1844 const Array<Array<size_t>> &basis,
1845 Coefficient *lambda_used = nullptr,
1846 double *gcv_score = nullptr)
1847 {
1848 const size_t m = data.size();
1849 const size_t b = basis.size();
1850
1851 ah_domain_error_if(m == 0) << "fit_ridge: data is empty";
1852 ah_domain_error_if(b == 0) << "fit_ridge: basis is empty";
1853
1854 // Build design matrix A [m x b]
1857
1858 for (size_t i = 0; i < m; ++i)
1859 {
1860 for (size_t j = 0; j < b; ++j)
1861 A(i)(j) = _eval_monomial(data(i).first, basis(j));
1862 y(i) = data(i).second;
1863 }
1864
1865 // Compute A^T A and A^T y
1868
1869 for (size_t i = 0; i < b; ++i)
1870 {
1871 for (size_t j = 0; j < b; ++j)
1872 {
1873 AtA(i)(j) = Coefficient{};
1874 for (size_t k = 0; k < m; ++k)
1875 AtA(i)(j) += A(k)(i) * A(k)(j);
1876 }
1877
1878 Aty(i) = Coefficient{};
1879 for (size_t k = 0; k < m; ++k)
1880 Aty(i) += A(k)(i) * y(k);
1881 }
1882
1883 // GCV-based lambda selection: test a range
1885 double best_gcv = std::numeric_limits<double>::max();
1886 constexpr int nlambda = 50;
1887
1888 for (int trial = 0; trial < nlambda; ++trial)
1889 {
1890 // Logarithmically spaced lambdas: 1e-6 to 1e6
1891 const double log_lambda = -6.0 + 12.0 * trial / (nlambda - 1.0);
1892 Coefficient lambda_try = Coefficient(std::pow(10.0, log_lambda));
1893
1894 // Solve (A^T A + lambda I) c = A^T y via Gaussian elimination
1896 Array<Coefficient> rhs = Aty;
1897
1898 // Add lambda to diagonal
1899 for (size_t i = 0; i < b; ++i)
1900 sys(i)(i) += lambda_try;
1901
1902 // Gaussian elimination with pivoting
1903 Array<Coefficient> c(b);
1904 bool singular = false;
1905
1906 for (size_t col = 0; col < b; ++col)
1907 {
1908 // Partial pivoting
1909 size_t prow = col;
1910 Coefficient pval = std::abs(sys(col)(col));
1911 for (size_t row = col + 1; row < b; ++row)
1912 if (Coefficient av = std::abs(sys(row)(col)); av > pval)
1913 {
1914 pval = av;
1915 prow = row;
1916 }
1917
1918 if (coeff_is_zero(sys(prow)(col)))
1919 {
1920 singular = true;
1921 break;
1922 }
1923
1924 if (prow != col)
1925 {
1926 std::swap(sys(col), sys(prow));
1927 std::swap(rhs(col), rhs(prow));
1928 }
1929
1930 // Eliminate
1931 for (size_t row = col + 1; row < b; ++row)
1932 {
1933 Coefficient f = sys(row)(col) / sys(col)(col);
1934 for (size_t j = col; j < b; ++j)
1935 sys(row)(j) -= f * sys(col)(j);
1936 rhs(row) -= f * rhs(col);
1937 }
1938 }
1939
1940 if (singular)
1941 continue;
1942
1943 // Back-substitution
1944 for (size_t i = b; i > 0; --i)
1945 {
1946 c(i - 1) = rhs(i - 1);
1947 for (size_t j = i; j < b; ++j)
1948 c(i - 1) -= sys(i - 1)(j) * c(j);
1949 c(i - 1) /= sys(i - 1)(i - 1);
1950 }
1951
1952 // Compute GCV score: sum((y - Ac)_i^2) / (1 - trace(A(A^T A + lambda I)^{-1} A^T) / m)^2
1954 for (size_t i = 0; i < m; ++i)
1955 {
1957 for (size_t j = 0; j < b; ++j)
1958 pred += A(i)(j) * c(j);
1959 Coefficient res = y(i) - pred;
1961 }
1962
1963 // Effective DOF approximation: trace(A(A^T A + lambda I)^{-1} A^T)
1964 // For simplicity, use b as upper bound for trace
1965 double denominator = 1.0 - static_cast<double>(b) / static_cast<double>(m);
1966 if (denominator < 0.01)
1967 denominator = 0.01;
1968
1969 double gcv_val = static_cast<double>(sum_sq_residuals) / (denominator * denominator);
1970
1971 if (gcv_val < best_gcv)
1972 {
1973 best_gcv = gcv_val;
1975 }
1976 }
1977
1978 // Solve final system with best_lambda
1981 for (size_t i = 0; i < b; ++i)
1982 final_sys(i)(i) += best_lambda;
1983
1985 for (size_t col = 0; col < b; ++col)
1986 {
1987 size_t prow = col;
1988 Coefficient pval = std::abs(final_sys(col)(col));
1989 for (size_t row = col + 1; row < b; ++row)
1990 if (Coefficient av = std::abs(final_sys(row)(col)); av > pval)
1991 {
1992 pval = av;
1993 prow = row;
1994 }
1995
1996 if (prow != col)
1997 {
1998 std::swap(final_sys(col), final_sys(prow));
1999 std::swap(final_rhs(col), final_rhs(prow));
2000 }
2001
2002 for (size_t row = col + 1; row < b; ++row)
2003 {
2005 for (size_t j = col; j < b; ++j)
2006 final_sys(row)(j) -= f * final_sys(col)(j);
2007 final_rhs(row) -= f * final_rhs(col);
2008 }
2009 }
2010
2011 for (size_t i = b; i > 0; --i)
2012 {
2013 c_final(i - 1) = final_rhs(i - 1);
2014 for (size_t j = i; j < b; ++j)
2015 c_final(i - 1) -= final_sys(i - 1)(j) * c_final(j);
2016 c_final(i - 1) /= final_sys(i - 1)(i - 1);
2017 }
2018
2019 // Output statistics if requested
2020 if (lambda_used != nullptr)
2022 if (gcv_score != nullptr)
2024
2025 // Assemble polynomial
2026 Gen_MultiPolynomial result(nvars);
2027 for (size_t j = 0; j < b; ++j)
2028 if (not coeff_is_zero(c_final(j)))
2029 result.add_to_coeff(basis(j), c_final(j));
2030
2031 return result;
2032 }
2033
2034 // =================================================================
2035 // Phase 2 — Differential Calculus
2036 // =================================================================
2037
2056 [[nodiscard]] Gen_MultiPolynomial partial(size_t var, size_t n = 1) const
2057 {
2058 if (n == 0)
2059 return *this;
2060 ah_domain_error_if(var >= nvars_) << "variable " << var << " >= nvars " << nvars_;
2061
2063 for_each_term([&](const Array<size_t> &alpha, const Coefficient &c)
2064 {
2065 const size_t k = alpha(var);
2066 if (k < n)
2067 return; // term vanishes
2068
2069 // Falling factorial: k * (k-1) * ... * (k-n+1)
2070 const Coefficient ff = multi_poly_detail::falling_factorial<Coefficient>(k, n);
2071 if (result.coeff_is_zero(ff))
2072 return;
2073
2074 const Coefficient new_c = c * ff;
2076 result.add_to_coeff(new_idx, new_c);
2077 });
2078 return result;
2079 }
2080
2088 {
2090 for (size_t k = 0; k < nvars_; ++k)
2091 result(k) = partial(k);
2092 return result;
2093 }
2094
2104 {
2105 const size_t n = nvars_;
2108 for (size_t i = 0; i < n; ++i)
2109 {
2110 H(i)(i) = partial(i, 2);
2111 for (size_t j = i + 1; j < n; ++j)
2112 {
2113 H(i)(j) = partial(i).partial(j);
2114 H(j)(i) = H(i)(j); // Symmetry
2115 }
2116 }
2117 return H;
2118 }
2119
2130 {
2132 << "eval_gradient: point size " << pt.size() << " < nvars " << nvars_;
2133
2134 const auto g = gradient();
2136 for (size_t k = 0; k < nvars_; ++k)
2137 result(k) = g(k).eval(pt);
2138 return result;
2139 }
2140
2151 {
2153 << "eval_hessian: point size " << pt.size() << " < nvars " << nvars_;
2154
2155 const auto H = hessian();
2156 const size_t n = nvars_;
2158 for (size_t i = 0; i < n; ++i)
2159 for (size_t j = 0; j < n; ++j)
2160 result(i)(j) = H(i)(j).eval(pt);
2161 return result;
2162 }
2163
2164 // =================================================================
2165 // Phase 2 — Interpolation (Newton Divided Differences)
2166 // =================================================================
2167
2185 const Array<Coefficient> &values,
2186 size_t nvars)
2187 {
2188 // Validate grid
2189 ah_domain_error_if(grid.size() != nvars)
2190 << "interpolate: grid size " << grid.size() << " != nvars " << nvars;
2191
2192 // Compute sizes and total points
2193 Array<size_t> sizes(nvars, size_t{0});
2194 size_t total = 1;
2195 for (size_t d = 0; d < nvars; ++d)
2196 {
2197 sizes(d) = grid(d).size();
2198 ah_domain_error_if(sizes(d) == 0) << "interpolate: grid[" << d << "] is empty";
2199 total *= sizes(d);
2200 }
2201
2202 ah_domain_error_if(values.size() != total)
2203 << "interpolate: values size " << values.size() << " != total " << total;
2204
2205 // Check for duplicate nodes in each dimension
2206 for (size_t d = 0; d < nvars; ++d)
2207 {
2208 const Array<Coefficient> &nodes = grid(d);
2209 for (size_t i = 0; i < nodes.size(); ++i)
2210 for (size_t j = i + 1; j < nodes.size(); ++j)
2212 << "interpolate: duplicate node at dimension " << d;
2213 }
2214
2215 // Initialize table: polynomials for each point (flat indexing)
2217 for (size_t f = 0; f < total; ++f)
2218 table(f) = Gen_MultiPolynomial(nvars, values(f));
2219
2220 // Process each dimension
2221 for (size_t dim = 0; dim < nvars; ++dim)
2222 {
2223 const size_t m = sizes(dim);
2224 const Array<Coefficient> &nodes = grid(dim);
2225
2226 // Compute stride for iterating in this dimension
2227 size_t stride = 1;
2228 for (size_t d = dim + 1; d < nvars; ++d)
2229 stride *= sizes(d);
2230
2231 // Number of slices
2232 const size_t num_slices = total / m;
2233
2234 // Process each slice
2235 for (size_t slice = 0; slice < num_slices; ++slice)
2236 {
2237 // Compute the base flat index for this slice
2238 size_t slice_flat = 0;
2239 size_t tmp = slice;
2240 size_t mult = 1;
2241 for (size_t d = nvars; d > 0; --d)
2242 {
2243 if (d - 1 == dim)
2244 continue;
2245 size_t sz = sizes(d - 1);
2246 slice_flat += (tmp % sz) * mult;
2247 tmp /= sz;
2248 mult *= sz;
2249 }
2250
2251 // Collect divided-differences table for this slice
2253 for (size_t i = 0; i < m; ++i)
2254 dd(i) = table(slice_flat + i * stride);
2255
2256 // Compute divided differences in-place
2257 for (size_t order = 1; order < m; ++order)
2258 for (size_t i = m; i > order; --i)
2259 {
2260 const Coefficient denom = nodes(i - 1) - nodes(i - 1 - order);
2261 dd(i - 1) = (dd(i - 1) - dd(i - 2)) / denom;
2262 }
2263
2264 // Reconstruct Newton polynomial in this dimension
2265 const auto xd = Gen_MultiPolynomial::variable(nvars, dim);
2266 Gen_MultiPolynomial result = dd(0);
2268 for (size_t i = 1; i < m; ++i)
2269 {
2270 basis = basis * (xd - Gen_MultiPolynomial(nvars, nodes(i - 1)));
2271 result = result + dd(i) * basis;
2272 }
2273
2274 // Write back to table
2275 table(slice_flat) = result;
2276 }
2277 }
2278
2279 return table(0);
2280 }
2281
2282 // =================================================================
2283 // Phase 2 — Serialization
2284 // =================================================================
2285
2294 [[nodiscard]] std::string to_json() const
2295 {
2296 std::ostringstream oss;
2297 oss << R"({"nvars":)" << nvars_ << R"(,"terms":[)";
2298 bool first = true;
2299 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
2300 {
2301 if (not first)
2302 oss << ",";
2303 oss << R"({"e":[)";
2304 for (size_t j = 0; j < idx.size(); ++j)
2305 {
2306 if (j > 0)
2307 oss << ",";
2308 oss << idx(j);
2309 }
2310 oss << R"(],"c":)" << std::setprecision(17) << c << "}";
2311 first = false;
2312 });
2313 oss << R"(]})";
2314 return oss.str();
2315 }
2316
2330 [[nodiscard]] static Gen_MultiPolynomial from_json(const std::string &s)
2331 {
2332 // Simple parser for our JSON format
2333 size_t nvars = 0;
2334 Gen_MultiPolynomial result;
2335
2336 // Find "nvars":n
2337 size_t pos = s.find("\"nvars\":");
2338 ah_domain_error_if(pos == std::string::npos) << "from_json: missing nvars";
2339 pos += 8;
2340 size_t pos_comma = s.find(',', pos);
2341 nvars = std::stoul(s.substr(pos, pos_comma - pos));
2342 result.nvars_ = nvars;
2343
2344 // Parse terms
2345 pos = s.find("\"terms\":[");
2346 ah_domain_error_if(pos == std::string::npos) << "from_json: missing terms";
2347 pos += 9;
2348
2349 while (pos < s.size() && s[pos] != ']')
2350 {
2351 // Find "e":[...]
2352 size_t e_pos = s.find("\"e\":[", pos);
2353 if (e_pos == std::string::npos)
2354 break;
2355 e_pos += 5;
2356 size_t e_end = s.find(']', e_pos);
2357 std::string e_str = s.substr(e_pos, e_end - e_pos);
2358
2359 // Parse exponents
2360 Array<size_t> idx(nvars, size_t{0});
2361 size_t idx_i = 0;
2362 std::istringstream iss_e(e_str);
2363 std::string token;
2364 while (std::getline(iss_e, token, ',') and idx_i < nvars)
2365 idx(idx_i++) = std::stoul(token);
2366
2367 // Find "c":value
2368 size_t c_pos = s.find("\"c\":", e_end);
2369 ah_domain_error_if(c_pos == std::string::npos) << "from_json: missing c";
2370 c_pos += 4;
2371 const size_t c_end = s.find('}', c_pos);
2372 Coefficient c_val = Coefficient(std::stod(s.substr(c_pos, c_end - c_pos)));
2373
2374 result.add_to_coeff(idx, c_val);
2375
2376 // Move to next term
2377 pos = s.find("},", e_end);
2378 if (pos == std::string::npos)
2379 break;
2380 pos += 2;
2381 }
2382
2383 return result;
2384 }
2385
2394 void to_binary(std::ostream &out) const
2395 {
2396 const uint64_t nv = nvars_;
2397 out.write(reinterpret_cast<const char *>(&nv), sizeof(nv));
2398
2399 const uint64_t nt = num_terms();
2400 out.write(reinterpret_cast<const char *>(&nt), sizeof(nt));
2401
2402 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
2403 {
2404 for (size_t j = 0; j < nvars_; ++j)
2405 {
2406 uint64_t exp = idx(j);
2407 out.write(reinterpret_cast<const char *>(&exp), sizeof(exp));
2408 }
2409 const auto c_double = static_cast<double>(c);
2410 out.write(reinterpret_cast<const char *>(&c_double), sizeof(c_double));
2411 });
2412 }
2413
2424 {
2425 uint64_t nv = 0;
2426 in.read(reinterpret_cast<char *>(&nv), sizeof(nv));
2427 size_t nvars = nv;
2428
2429 uint64_t nt = 0;
2430 in.read(reinterpret_cast<char *>(&nt), sizeof(nt));
2431 size_t n_terms = nt;
2432
2433 Gen_MultiPolynomial result(nvars);
2434 for (size_t i = 0; i < n_terms; ++i)
2435 {
2436 Array<size_t> idx(nvars, size_t{0});
2437 for (size_t j = 0; j < nvars; ++j)
2438 {
2439 uint64_t exp = 0;
2440 in.read(reinterpret_cast<char *>(&exp), sizeof(exp));
2441 idx(j) = exp;
2442 }
2443 double c_double = 0.0;
2444 in.read(reinterpret_cast<char *>(&c_double), sizeof(c_double));
2446
2447 result.add_to_coeff(idx, c);
2448 }
2449
2450 return result;
2451 }
2452
2453 // =================================================================
2454 // Phase 2 — Parallelism
2455 // =================================================================
2456
2467 {
2468 const size_t n = pts.size();
2470
2471 // Sequential evaluation (parallelism can be added via pmaps in future)
2472 for (size_t i = 0; i < n; ++i)
2473 results(i) = eval(pts(i));
2474
2475 return results;
2476 }
2477
2493 const Array<std::pair<Array<Coefficient>, Coefficient>> &data,
2494 size_t nvars,
2495 const Array<Array<size_t>> &basis)
2496 {
2497 const size_t m = data.size();
2498 const size_t b = basis.size();
2499
2500 ah_domain_error_if(m == 0) << "fit_parallel: data is empty";
2501 ah_domain_error_if(b == 0) << "fit_parallel: basis is empty";
2502
2503 // Convert data to std::vector for pmaps
2504 std::vector<std::pair<Array<Coefficient>, Coefficient>> data_vec(m);
2505 for (size_t i = 0; i < m; ++i)
2506 data_vec[i] = data(i);
2507
2508 // Convert basis to std::vector for capture
2509 std::vector<Array<size_t>> basis_vec(b);
2510 for (size_t i = 0; i < b; ++i)
2511 basis_vec[i] = basis(i);
2512
2513 // Build design matrix rows in parallel
2514 auto rows = pmaps(data_vec,
2515 [&basis_vec, b](const std::pair<Array<Coefficient>, Coefficient> &p)
2516 {
2518 for (size_t j = 0; j < b; ++j)
2520 return row;
2521 });
2522
2523 // Normal equations (sequential — bottleneck is row construction)
2526 for (size_t i = 0; i < m; ++i)
2527 {
2528 A(i) = rows[i];
2529 y(i) = data(i).second;
2530 }
2531
2532 // Compute A^T A and A^T y
2535
2536 for (size_t i = 0; i < b; ++i)
2537 {
2538 for (size_t j = 0; j < b; ++j)
2539 {
2540 AtA(i)(j) = Coefficient{};
2541 for (size_t k = 0; k < m; ++k)
2542 AtA(i)(j) += A(k)(i) * A(k)(j);
2543 }
2544
2545 Aty(i) = Coefficient{};
2546 for (size_t k = 0; k < m; ++k)
2547 Aty(i) += A(k)(i) * y(k);
2548 }
2549
2550 // Gaussian elimination with pivoting
2551 Array<Coefficient> c(b);
2552 for (size_t col = 0; col < b; ++col)
2553 {
2554 size_t prow = col;
2555 Coefficient pval = std::abs(AtA(col)(col));
2556 for (size_t row = col + 1; row < b; ++row)
2557 {
2558 Coefficient av = std::abs(AtA(row)(col));
2559 if (av > pval)
2560 {
2561 pval = av;
2562 prow = row;
2563 }
2564 }
2565
2567 << "fit_parallel: singular normal equations";
2568
2569 if (prow != col)
2570 {
2571 std::swap(AtA(col), AtA(prow));
2572 std::swap(Aty(col), Aty(prow));
2573 }
2574
2575 for (size_t row = col + 1; row < b; ++row)
2576 {
2577 Coefficient f = AtA(row)(col) / AtA(col)(col);
2578 for (size_t j = col; j < b; ++j)
2579 AtA(row)(j) -= f * AtA(col)(j);
2580 Aty(row) -= f * Aty(col);
2581 }
2582 }
2583
2584 // Back-substitution
2585 for (size_t i = b; i > 0; --i)
2586 {
2587 c(i - 1) = Aty(i - 1);
2588 for (size_t j = i; j < b; ++j)
2589 c(i - 1) -= AtA(i - 1)(j) * c(j);
2590 c(i - 1) /= AtA(i - 1)(i - 1);
2591 }
2592
2593 // Assemble polynomial
2594 Gen_MultiPolynomial result(nvars);
2595 for (size_t j = 0; j < b; ++j)
2597 result.add_to_coeff(basis(j), c(j));
2598
2599 return result;
2600 }
2601
2602 // =================================================================
2603 // Layer 3 — Gröbner Bases and Ideal Operations
2604 // =================================================================
2605
2625 [[nodiscard]] std::pair<Array<Gen_MultiPolynomial>, Gen_MultiPolynomial> divmod(
2627 {
2628 size_t s = divisors.size();
2629 ah_domain_error_if(s == 0) << "divmod: empty divisors array";
2630
2631 for (size_t i = 0; i < s; ++i)
2632 {
2633 ah_domain_error_if(divisors(i).is_zero()) << "divmod: divisors[" << i << "] is zero";
2635 << "divmod: divisors[" << i << "].nvars (" << divisors(i).nvars_ << ") != this.nvars ("
2636 << nvars_ << ")";
2637 }
2638
2639 // Initialize quotient array and remainder
2642 Gen_MultiPolynomial p = *this;
2643
2644 // Buchberger's multivariate division algorithm
2645 while (not p.is_zero())
2646 {
2647 auto [lm_p, lc_p] = p.leading_term();
2648 bool found = false;
2649
2650 for (size_t i = 0; i < s; ++i)
2651 {
2652 if (divisors(i).is_zero())
2653 continue;
2654
2655 auto [lm_fi, lc_fi] = divisors(i).leading_term();
2656
2658 {
2659 if constexpr (std::is_integral_v<Coefficient>)
2660 {
2661 if (lc_p % lc_fi != Coefficient{})
2662 continue;
2663 }
2664
2668 continue;
2669 q(i).add_to_coeff(t_idx, t_coeff);
2670 p -= monomial(nvars_, t_idx, t_coeff) * divisors(i);
2671 found = true;
2672 break;
2673 }
2674 }
2675
2676 if (not found)
2677 {
2678 r.add_to_coeff(lm_p, lc_p);
2679 p -= monomial(nvars_, lm_p, lc_p);
2680 }
2681 }
2682
2683 return {q, r};
2684 }
2685
2701 const Gen_MultiPolynomial &g)
2702 requires(not std::is_integral_v<Coefficient>)
2703 {
2704 ah_domain_error_if(f.is_zero()) << "s_poly: first argument is zero";
2705 ah_domain_error_if(g.is_zero()) << "s_poly: second argument is zero";
2706 ah_invalid_argument_if(f.nvars_ != g.nvars_)
2707 << "s_poly: incompatible number of variables (" << f.nvars_ << " vs " << g.nvars_ << ")";
2708
2709 const size_t nv = f.nvars_;
2710 auto [lm_f, lc_f] = f.leading_term();
2711 auto [lm_g, lc_g] = g.leading_term();
2712
2714
2718
2722
2723 return t_f * f - t_g * g;
2724 }
2725
2753 requires(not std::is_integral_v<Coefficient>)
2754 {
2755 const size_t gen_count = generators.size();
2756 ah_domain_error_if(gen_count == 0) << "groebner_basis: empty generators";
2757
2758 for (size_t i = 0; i < gen_count; ++i)
2759 ah_domain_error_if(generators(i).is_zero()) << "groebner_basis: generator[" << i << "] is zero";
2760
2762
2763 ah_domain_error_if(G.is_empty()) << "groebner_basis: generators reduce to the zero basis";
2764
2766 Pair_Queue P;
2770
2771 size_t iteration = 0;
2772
2773 // Main Buchberger loop
2774 while (not P.is_empty())
2775 {
2776 constexpr size_t max_iterations = 10000;
2777 ah_domain_error_if(++iteration > max_iterations)
2778 << "groebner_basis: iteration limit (" << max_iterations << ") exceeded";
2779
2781 const size_t i = candidate.i;
2782 const size_t j = candidate.j;
2783
2784 // B1 criterion: if LMs are coprime (all min(lm_i, lm_j) = 0), skip
2787
2789 {
2790 zero_pairs.insert(canonical_pair(i, j), true);
2791 continue;
2792 }
2793
2795 {
2796 zero_pairs.insert(canonical_pair(i, j), true);
2797 continue;
2798 }
2799
2800 // Reduce S-polynomial
2801 Gen_MultiPolynomial s = s_poly(G(i), G(j));
2802 auto [qs, rs] = s.divmod(G);
2804
2805 // If remainder is non-zero, add to basis and pair with all existing elements
2806 if (not r.is_zero())
2807 {
2810 continue;
2811
2812 G.append(std::move(r));
2813 G = autoreduce_groebner_basis(std::move(G));
2815 << "groebner_basis: incremental autoreduction collapsed the basis";
2817 continue;
2818 }
2819 zero_pairs.insert(canonical_pair(i, j), true);
2820 }
2821
2822 return G;
2823 }
2824
2842 requires(not std::is_integral_v<Coefficient>)
2843 {
2844 // Phase 1: Compute Gröbner basis and drop redundant leading monomials.
2846
2847 // Phase 2: Make monic
2848 for (size_t i = 0; i < G.size(); ++i)
2850
2851 // Phase 3: Interreduce
2852 for (size_t i = 0; i < G.size(); ++i)
2853 {
2854 if (G.size() == 1)
2855 continue; // Skip if only one element
2856
2857 // Build G_minus_i: all elements except i
2859 size_t idx = 0;
2860 for (size_t j = 0; j < G.size(); ++j)
2861 if (j != i)
2862 G_minus_i(idx++) = G(j);
2863
2864 if (not G_minus_i.is_empty())
2865 {
2866 auto [q, r] = G(i).divmod(G_minus_i);
2867 G(i) = std::move(r);
2869 }
2870 }
2871
2873 for (size_t i = 0; i < G.size(); ++i)
2875
2876 return minimize_groebner_basis(std::move(G));
2877 }
2878
2893
2911 requires(not std::is_integral_v<Coefficient>)
2912 {
2913 if (f.is_zero())
2914 return true;
2915 return f.divmod(groebner_basis(generators)).second.is_zero();
2916 }
2917
2918 // =================================================================
2919 // Layer 4 — Ideal Arithmetic
2920 // =================================================================
2921
2937 {
2938 ah_domain_error_if(a.size() == 0) << "ideal_sum: first array is empty";
2939 ah_domain_error_if(b.size() == 0) << "ideal_sum: second array is empty";
2940
2941 for (size_t i = 0; i < a.size(); ++i)
2942 ah_domain_error_if(a(i).is_zero()) << "ideal_sum: a[" << i << "] is zero";
2943
2944 for (size_t j = 0; j < b.size(); ++j)
2945 ah_domain_error_if(b(j).is_zero()) << "ideal_sum: b[" << j << "] is zero";
2946
2947 size_t nv = a(0).nvars_;
2948 for (size_t i = 1; i < a.size(); ++i)
2949 ah_invalid_argument_if(a(i).nvars_ != nv) << "ideal_sum: nvars mismatch in a[" << i
2950 << "]: expected " << nv << ", got " << a(i).nvars_;
2951
2952 for (size_t j = 0; j < b.size(); ++j)
2953 ah_invalid_argument_if(b(j).nvars_ != nv) << "ideal_sum: nvars mismatch in b[" << j
2954 << "]: expected " << nv << ", got " << b(j).nvars_;
2955
2957 for (size_t i = 0; i < a.size(); ++i)
2958 result(i) = a(i);
2959 for (size_t j = 0; j < b.size(); ++j)
2960 result(a.size() + j) = b(j);
2961
2962 return result;
2963 }
2964
2980 {
2981 ah_domain_error_if(a.size() == 0) << "ideal_product: first array is empty";
2982 ah_domain_error_if(b.size() == 0) << "ideal_product: second array is empty";
2983
2984 for (size_t i = 0; i < a.size(); ++i)
2985 ah_domain_error_if(a(i).is_zero()) << "ideal_product: a[" << i << "] is zero";
2986
2987 for (size_t j = 0; j < b.size(); ++j)
2988 ah_domain_error_if(b(j).is_zero()) << "ideal_product: b[" << j << "] is zero";
2989
2990 size_t nv = a(0).nvars_;
2991 for (size_t i = 1; i < a.size(); ++i)
2992 ah_invalid_argument_if(a(i).nvars_ != nv) << "ideal_product: nvars mismatch in a[" << i
2993 << "]: expected " << nv << ", got " << a(i).nvars_;
2994
2995 for (size_t j = 0; j < b.size(); ++j)
2996 ah_invalid_argument_if(b(j).nvars_ != nv) << "ideal_product: nvars mismatch in b[" << j
2997 << "]: expected " << nv << ", got " << b(j).nvars_;
2998
3000 size_t k = 0;
3001 for (size_t i = 0; i < a.size(); ++i)
3002 for (size_t j = 0; j < b.size(); ++j)
3003 result(k++) = a(i) * b(j);
3004
3005 return result;
3006 }
3007
3022 size_t n)
3023 {
3024 ah_domain_error_if(gens.size() == 0) << "ideal_power: empty generators";
3025
3026 for (size_t i = 0; i < gens.size(); ++i)
3027 ah_domain_error_if(gens(i).is_zero()) << "ideal_power: gens[" << i << "] is zero";
3028
3029 size_t nv = gens(0).nvars_;
3030
3031 // Special case: I^0 = ⟨1⟩
3032 if (n == 0)
3033 {
3036 return unit;
3037 }
3038
3039 // Special case: I^1 = I
3040 if (n == 1)
3041 return gens;
3042
3043 // General case: iteratively multiply
3045 for (size_t i = 1; i < n; ++i)
3046 result = ideal_product(result, gens);
3047
3048 return result;
3049 }
3050
3069 requires(not std::is_integral_v<Coefficient>)
3070 {
3071 ah_domain_error_if(I_gens.size() == 0) << "contains_ideal: I is empty";
3072 ah_domain_error_if(J_gens.size() == 0) << "contains_ideal: J is empty";
3073
3074 for (size_t i = 0; i < I_gens.size(); ++i)
3075 ah_domain_error_if(I_gens(i).is_zero()) << "contains_ideal: I[" << i << "] is zero";
3076
3077 for (size_t j = 0; j < J_gens.size(); ++j)
3078 ah_domain_error_if(J_gens(j).is_zero()) << "contains_ideal: J[" << j << "] is zero";
3079
3080 size_t nv = I_gens(0).nvars_;
3081 for (size_t i = 1; i < I_gens.size(); ++i)
3083 << "contains_ideal: nvars mismatch in I[" << i << "]: expected " << nv << ", got "
3084 << I_gens(i).nvars_;
3085
3086 for (size_t j = 0; j < J_gens.size(); ++j)
3088 << "contains_ideal: nvars mismatch in J[" << j << "]: expected " << nv << ", got "
3089 << J_gens(j).nvars_;
3090
3091 // Precompute Gröbner basis of I once
3093
3094 // Check membership of each generator of J in I
3095 for (size_t j = 0; j < J_gens.size(); ++j)
3096 {
3098 if (not remainder.is_zero())
3099 return false;
3100 }
3101
3102 return true;
3103 }
3104
3121 requires(not std::is_integral_v<Coefficient>)
3122 {
3123 ah_domain_error_if(I_gens.size() == 0) << "ideals_equal: I is empty";
3124 ah_domain_error_if(J_gens.size() == 0) << "ideals_equal: J is empty";
3125
3126 for (size_t i = 0; i < I_gens.size(); ++i)
3127 ah_domain_error_if(I_gens(i).is_zero()) << "ideals_equal: I[" << i << "] is zero";
3128
3129 for (size_t j = 0; j < J_gens.size(); ++j)
3130 ah_domain_error_if(J_gens(j).is_zero()) << "ideals_equal: J[" << j << "] is zero";
3131
3132 size_t nv = I_gens(0).nvars_;
3133 for (size_t i = 1; i < I_gens.size(); ++i)
3135 << "ideals_equal: nvars mismatch in I[" << i << "]: expected " << nv << ", got "
3136 << I_gens(i).nvars_;
3137
3138 for (size_t j = 0; j < J_gens.size(); ++j)
3140 << "ideals_equal: nvars mismatch in J[" << j << "]: expected " << nv << ", got "
3141 << J_gens(j).nvars_;
3142
3144 }
3145
3165 requires(not std::is_integral_v<Coefficient>)
3166 {
3167 ah_domain_error_if(gens.size() == 0) << "radical_member: empty generators";
3168
3169 for (size_t i = 0; i < gens.size(); ++i)
3170 ah_domain_error_if(gens(i).is_zero()) << "radical_member: gens[" << i << "] is zero";
3171
3172 size_t nv = gens(0).nvars_;
3173
3174 // Check nvars consistency (f.nvars_ == 0 is OK for zero polynomial)
3175 ah_invalid_argument_if(not f.is_zero() and f.nvars_ != nv)
3176 << "radical_member: f nvars mismatch: expected " << nv << ", got " << f.nvars_;
3177
3178 // Special case: 0 ∈ √I always
3179 if (f.is_zero())
3180 return true;
3181
3182 // Augmented ring has nv+1 variables (the new variable y is at index nv)
3183 size_t nv_aug = nv + 1;
3184
3185 // Promote generators to the augmented ring
3187 for (size_t i = 0; i < gens.size(); ++i)
3188 aug_gens(i) = gens(i).promote(nv_aug);
3189
3190 // Create the polynomial (1 - y·f) in the augmented ring
3191 // y = monomial with exponent [0, ..., 0, 1] at index nv
3192 Array<size_t> y_exp(nv_aug, static_cast<size_t>(0));
3193 y_exp(nv) = 1;
3195
3196 // Create 1 in the augmented ring
3198
3199 // Promote f to augmented ring and compute (1 - y·f)
3201 aug_gens(gens.size()) = one_aug - y_poly * f_aug;
3202
3203 // Check if 1 is in the augmented ideal
3204 return ideal_member(one_aug, aug_gens);
3205 }
3206
3207 // =================================================================
3208 // Layer 6 — Multivariate Factorization
3209 // =================================================================
3210
3221
3238 {
3239 if (coeffs.is_empty())
3240 return Coefficient{};
3241
3242 Coefficient result = Coefficient{};
3243 for (auto it = coeffs.get_it(); it.has_curr(); it.next_ne())
3244 {
3245 const auto &p = it.get_curr();
3246 Coefficient a = p.second < Coefficient{} ? -p.second : p.second;
3247 result = std::gcd(result, a);
3248 }
3249
3250 // Sign follows leading coefficient
3251 if (not is_zero())
3252 {
3254 if (lc < Coefficient{} and result > Coefficient{})
3255 result = -result;
3256 }
3257
3258 return result;
3259 }
3260
3274 {
3275 if (is_zero())
3277
3278 Coefficient c = content();
3279 if (c == Coefficient{})
3280 return *this;
3281
3282 if (c == Coefficient(1))
3283 return *this;
3284
3285 if (c == Coefficient(-1))
3286 {
3288 for_each_term([&](const Array<size_t> &idx, const Coefficient &coeff)
3289 {
3290 r.coeffs.insert(idx, -coeff);
3291 });
3292 return r;
3293 }
3294
3296 for_each_term([&](const Array<size_t> &idx, const Coefficient &coeff)
3297 {
3298 if (Coefficient q = coeff / c; not coeff_is_zero(q))
3299 result.coeffs.insert(idx, q);
3300 });
3301 return result;
3302 }
3303
3320 const Array<Coefficient> &eval_pts) const
3321 {
3323 << "homomorphic_eval: keep_var " << keep_var << " >= nvars " << nvars_;
3324 ah_domain_error_if(eval_pts.size() != nvars_ - 1)
3325 << "homomorphic_eval: eval_pts size " << eval_pts.size() << " != nvars-1 " << (nvars_ - 1);
3326
3328
3329 for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
3330 {
3332 size_t pt_idx = 0;
3333 for (size_t v = 0; v < nvars_; ++v)
3334 {
3335 if (v == keep_var)
3336 continue;
3337 if (idx(v) != 0)
3339 ++pt_idx;
3340 }
3341
3343 result.set_coeff(idx(keep_var), result.get_coeff(idx(keep_var)) + term_val);
3344 });
3345
3346 return result;
3347 }
3348
3357 size_t nv,
3358 const size_t x_var)
3359 {
3360 Gen_MultiPolynomial result(nv);
3361 u.for_each_term([&](const size_t exp, const Coefficient &c)
3362 {
3363 Array<size_t> idx(nv, size_t{0});
3364 idx(x_var) = exp;
3365 result.add_to_coeff(idx, c);
3366 });
3367 return result;
3368 }
3369
3372 {
3373 for (size_t i = 0; i < vals.size(); ++i)
3374 if (vals(i) == value)
3375 return;
3376 vals.append(value);
3377 }
3378
3385
3388 {
3390 {
3391 if (lhs.main_coeff < rhs.main_coeff)
3392 return true;
3393 if (rhs.main_coeff < lhs.main_coeff)
3394 return false;
3395 return lhs.constant_term < rhs.constant_term;
3396 }
3397 };
3398
3402 requires std::is_integral_v<Coefficient>
3403 {
3405
3407 auto factors = u.factorize();
3408 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3409 {
3410 const auto &term = it.get_curr();
3411 if (term.factor.degree() != 1)
3412 continue;
3413
3414 UniPoly primitive = term.factor.primitive_part();
3415 for (size_t m = 0; m < term.multiplicity; ++m)
3416 result.append(Uni_Linear_Factor_Data{primitive.leading_coeff(), primitive.get_coeff(0)});
3417 }
3418
3419 if (result.size() > 1)
3420 Aleph::in_place_sort(result, Uni_Linear_Factor_Data_Less{});
3421
3422 return result;
3423 }
3424
3428 requires std::is_integral_v<Coefficient>
3429 {
3430 Array<Coefficient> roots;
3431 auto factors = u.factorize();
3432 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3433 {
3434 const auto &term = it.get_curr();
3435 if (term.factor.degree() != 1 or term.factor.get_coeff(1) != Coefficient(1))
3436 continue;
3437 _append_unique(roots, -term.factor.get_coeff(0));
3438 }
3439 return roots;
3440 }
3441
3443 [[nodiscard]] static size_t _eval_index_for_var(size_t main_var, size_t var) noexcept
3444 {
3445 return var < main_var ? var : var - 1;
3446 }
3447
3450 {
3451 size_t count = 0;
3452 last_active_var = 0;
3453 for (size_t v = 0; v < p.num_vars(); ++v)
3454 if (p.degree_in(v) > 0)
3455 {
3456 ++count;
3457 last_active_var = v;
3458 }
3459 return count;
3460 }
3461
3473 size_t nvars,
3474 size_t main_var,
3477 const Coefficient &main_coeff,
3480 requires std::is_integral_v<Coefficient>
3481 {
3482 Gen_MultiPolynomial factor(nvars);
3483
3484 Array<size_t> idx(nvars, size_t{0});
3485 idx(main_var) = 1;
3486 factor.add_to_coeff(idx, main_coeff);
3487
3489 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3490 {
3491 const Coefficient coeff = other_coeffs(pos);
3492 intercept -= coeff * base_eval_pts(_eval_index_for_var(main_var, other_vars(pos)));
3493
3494 if (coeff_is_zero(coeff))
3495 continue;
3496
3497 Array<size_t> var_idx(nvars, size_t{0});
3498 var_idx(other_vars(pos)) = 1;
3499 factor.add_to_coeff(var_idx, coeff);
3500 }
3501
3502 if (not coeff_is_zero(intercept))
3503 factor.add_to_coeff(Array<size_t>(nvars, size_t{0}), intercept);
3504
3505 return factor;
3506 }
3507
3510 size_t main_var,
3512 Gen_MultiPolynomial &factor)
3513 requires std::is_integral_v<Coefficient>
3514 {
3516
3517 UniPoly base_univ = p.homomorphic_eval(main_var, base_eval_pts);
3518 Array<Uni_Linear_Factor_Data> base_factors = _collect_sorted_primitive_linear_factors(base_univ);
3519 if (base_factors.is_empty())
3520 return false;
3521
3523 for (size_t v = 0; v < p.num_vars(); ++v)
3524 if (v != main_var and p.degree_in(v) > 0)
3525 other_vars.append(v);
3526
3527 if (other_vars.is_empty())
3528 return false;
3529
3532 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3533 {
3535 eval_pts(_eval_index_for_var(main_var, other_vars(pos))) += Coefficient(1);
3536
3537 UniPoly perturbed = p.homomorphic_eval(main_var, eval_pts);
3538 perturbed_factors(pos) = _collect_sorted_primitive_linear_factors(perturbed);
3539 if (perturbed_factors(pos).size() != base_factors.size())
3540 return false;
3541 }
3542
3543 for (size_t slot = 0; slot < base_factors.size(); ++slot)
3544 {
3545 const Coefficient main_coeff = base_factors(slot).main_coeff;
3546 const Coefficient base_constant_term = base_factors(slot).constant_term;
3548
3549 bool consistent = true;
3550 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3551 {
3552 const auto &perturbed = perturbed_factors(pos)(slot);
3553 if (perturbed.main_coeff != main_coeff)
3554 {
3555 consistent = false;
3556 break;
3557 }
3558
3559 other_coeffs(pos) = perturbed.constant_term - base_constant_term;
3560 }
3561
3562 if (not consistent)
3563 continue;
3564
3565 Gen_MultiPolynomial candidate = _build_affine_factor_from_linear_data(p.num_vars(),
3566 main_var,
3567 other_vars,
3569 main_coeff,
3572
3573 if (candidate.is_constant())
3574 continue;
3575
3576 if (_divides_exactly(p, candidate))
3577 {
3578 factor = std::move(candidate);
3579 return true;
3580 }
3581 }
3582
3583 return false;
3584 }
3585
3593 size_t main_var,
3597 const Coefficient &base_root)
3598 requires std::is_integral_v<Coefficient>
3599 {
3600 Gen_MultiPolynomial factor(nvars);
3601
3602 Array<size_t> idx(nvars, size_t{0});
3603 idx(main_var) = 1;
3604 factor.add_to_coeff(idx, Coefficient(1));
3605
3607 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3608 {
3609 intercept -= slopes(pos) * base_eval_pts(_eval_index_for_var(main_var, other_vars(pos)));
3610
3611 if (coeff_is_zero(slopes(pos)))
3612 continue;
3613
3614 Array<size_t> var_idx(nvars, size_t{0});
3615 var_idx(other_vars(pos)) = 1;
3616 factor.add_to_coeff(var_idx, -slopes(pos));
3617 }
3618
3619 if (not coeff_is_zero(intercept))
3620 factor.add_to_coeff(Array<size_t>(nvars, size_t{0}), -intercept);
3621
3622 return factor;
3623 }
3624
3627 size_t main_var,
3631 const Coefficient &base_root,
3632 size_t pos,
3634 Gen_MultiPolynomial &factor)
3635 requires std::is_integral_v<Coefficient>
3636 {
3637 if (pos == other_vars.size())
3638 {
3640 = _build_affine_factor(p.num_vars(), main_var, other_vars, base_eval_pts, slopes, base_root);
3641
3642 if (candidate.is_constant())
3643 return false;
3644
3645 if (_divides_exactly(p, candidate))
3646 {
3647 factor = std::move(candidate);
3648 return true;
3649 }
3650
3651 return false;
3652 }
3653
3654 for (size_t i = 0; i < root_options(pos).size(); ++i)
3655 {
3656 slopes(pos) = root_options(pos)(i) - base_root;
3657 if (_search_affine_lift_options(
3659 return true;
3660 }
3661
3662 return false;
3663 }
3664
3667 size_t main_var,
3669 Gen_MultiPolynomial &factor)
3670 requires std::is_integral_v<Coefficient>
3671 {
3673
3674 if (_try_lift_primitive_affine_linear_factor_for_main_var(p, main_var, base_eval_pts, factor))
3675 return true;
3676
3677 UniPoly base_univ = p.homomorphic_eval(main_var, base_eval_pts);
3678 Array<Coefficient> base_roots = _collect_unique_monic_linear_roots(base_univ);
3679 if (base_roots.is_empty())
3680 return false;
3681
3683 for (size_t v = 0; v < p.num_vars(); ++v)
3684 if (v != main_var and p.degree_in(v) > 0)
3685 other_vars.append(v);
3686
3687 if (other_vars.is_empty())
3688 return false;
3689
3691 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3692 {
3694 eval_pts(_eval_index_for_var(main_var, other_vars(pos))) += Coefficient(1);
3695
3696 UniPoly perturbed = p.homomorphic_eval(main_var, eval_pts);
3697 root_options(pos) = _collect_unique_monic_linear_roots(perturbed);
3698 if (root_options(pos).is_empty())
3699 return false;
3700 }
3701
3703 for (size_t i = 0; i < base_roots.size(); ++i)
3704 if (_search_affine_lift_options(
3706 return true;
3707
3708 return false;
3709 }
3710
3713 Gen_MultiPolynomial &factor)
3714 requires std::is_integral_v<Coefficient>
3715 {
3716 for (size_t main_var = 0; main_var < p.num_vars(); ++main_var)
3717 {
3718 if (p.degree_in(main_var) == 0)
3719 continue;
3720
3721 Array<Coefficient> base_eval_pts(p.num_vars() - 1, Coefficient{});
3722 if (_try_lift_affine_linear_factor_for_main_var(p, main_var, base_eval_pts, factor))
3723 return true;
3724
3725 Coefficient val(1);
3726 for (size_t v = 0; v < p.num_vars(); ++v)
3727 {
3728 if (v == main_var)
3729 continue;
3730 base_eval_pts(_eval_index_for_var(main_var, v)) = val;
3731 val = val + Coefficient(2);
3732 }
3733
3734 if (_try_lift_affine_linear_factor_for_main_var(p, main_var, base_eval_pts, factor))
3735 return true;
3736 }
3737
3738 return false;
3739 }
3740
3744 Coefficient start,
3745 Coefficient step,
3747 size_t &total_points)
3748 requires std::is_integral_v<Coefficient>
3749 {
3750 static constexpr size_t max_grid_points = 81;
3751
3753 total_points = 1;
3754
3755 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3756 {
3757 const size_t degree_bound = p.degree_in(other_vars(pos));
3758 const size_t node_count = degree_bound + 1;
3759 if (node_count == 0)
3760 return false;
3761
3762 if (total_points > max_grid_points / node_count)
3763 return false;
3764 total_points *= node_count;
3765
3766 grid(pos).reserve(node_count);
3767 Coefficient value = start;
3768 for (size_t i = 0; i < node_count; ++i)
3769 {
3770 grid(pos).append(value);
3771 value = value + step;
3772 }
3773 }
3774
3775 return total_points > 0;
3776 }
3777
3780 size_t flat_index)
3781 {
3782 Array<Coefficient> point(grid.size(), Coefficient{});
3783 size_t remaining = flat_index;
3784 for (size_t pos = grid.size(); pos > 0; --pos)
3785 {
3786 const size_t idx = remaining % grid(pos - 1).size();
3787 remaining /= grid(pos - 1).size();
3788 point(pos - 1) = grid(pos - 1)(idx);
3789 }
3790 return point;
3791 }
3792
3795 size_t main_var,
3798 {
3800 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3801 eval_pts(_eval_index_for_var(main_var, other_vars(pos))) = coords(pos);
3802 return eval_pts;
3803 }
3804
3808 requires std::is_integral_v<Coefficient>
3809 {
3811
3813 auto terms = u.factorize();
3814 for (auto it = terms.get_it(); it.has_curr(); it.next_ne())
3815 {
3816 const auto &term = it.get_curr();
3817 for (size_t m = 0; m < term.multiplicity; ++m)
3818 expanded.append(term.factor);
3819 }
3820 return expanded;
3821 }
3822
3825 {
3827 {
3828 if (lhs.degree() != rhs.degree())
3829 return lhs.degree() < rhs.degree();
3830
3831 const Coefficient lhs_lc = lhs.leading_coeff();
3832 const Coefficient rhs_lc = rhs.leading_coeff();
3833
3834 for (size_t exp = lhs.degree(); exp > 0; --exp)
3835 {
3836 const Coefficient lhs_coeff = lhs.get_coeff(exp - 1);
3837 const Coefficient rhs_coeff = rhs.get_coeff(exp - 1);
3838
3839# if defined(_MSC_VER) && !defined(__clang__)
3840 const int norm_cmp = multi_poly_detail::compare_integral_products(lhs_coeff, rhs_lc,
3841 rhs_coeff, lhs_lc);
3842# else
3843 const __int128 lhs_norm = static_cast<__int128>(lhs_coeff) * rhs_lc;
3844 const __int128 rhs_norm = static_cast<__int128>(rhs_coeff) * lhs_lc;
3845 const int norm_cmp = lhs_norm < rhs_norm ? -1 : (rhs_norm < lhs_norm ? 1 : 0);
3846# endif
3847 if (norm_cmp < 0)
3848 return true;
3849 if (norm_cmp > 0)
3850 return false;
3851 }
3852
3853 return lhs_lc < rhs_lc;
3854 }
3855 };
3856
3859 size_t max_degree,
3861 requires std::is_integral_v<Coefficient>
3862 {
3864
3865 groups = Array<Array<UniPoly>>(max_degree + 1, Array<UniPoly>());
3866
3867 Array<UniPoly> expanded = _expanded_univariate_factors(u);
3868 for (size_t i = 0; i < expanded.size(); ++i)
3869 {
3870 UniPoly factor = expanded(i);
3871 if (factor.is_zero() or factor.is_constant())
3872 continue;
3873 factor = factor.primitive_part();
3874 groups(factor.degree()).append(factor);
3875 }
3876
3878 for (size_t degree = 0; degree < groups.size(); ++degree)
3879 if (groups(degree).size() > 1)
3881
3882 return true;
3883 }
3884
3888 size_t nvars,
3889 size_t main_var,
3891 size_t exp)
3892 {
3893 Gen_MultiPolynomial result(nvars);
3894 coeff_poly.for_each_term([&](const Array<size_t> &idx_sub, const Coefficient &c)
3895 {
3896 Array<size_t> idx_full(nvars, size_t{0});
3898 for (size_t pos = 0; pos < other_vars.size(); ++pos)
3899 idx_full(other_vars(pos)) = idx_sub(pos);
3900 result.add_to_coeff(idx_full, c);
3901 });
3902 return result;
3903 }
3904
3915 requires std::is_integral_v<Coefficient>
3916 {
3918
3920
3922 for (size_t v = 0; v < p.num_vars(); ++v)
3923 if (v != main_var and p.degree_in(v) > 0)
3924 other_vars.append(v);
3925
3926 if (other_vars.is_empty())
3927 return result;
3928
3930 size_t total_points = 0;
3931 if (not _build_interpolation_grid(p, other_vars, grid_start, grid_step, grid, total_points))
3932 return result;
3933
3935 {
3936 Array<Coefficient> base_coords = _decode_grid_point(grid, 0);
3938 = _build_eval_point(p.num_vars(), main_var, other_vars, base_coords);
3939 UniPoly base_univ = p.homomorphic_eval(main_var, eval_pts);
3940 if (base_univ.is_zero() or base_univ.is_constant())
3941 return result;
3942 if (not _collect_sorted_primitive_factor_groups(base_univ, p.degree_in(main_var), base_groups))
3943 return result;
3944 }
3945
3948 for (size_t degree = 1; degree < base_groups.size(); ++degree)
3949 for (size_t slot = 0; slot < base_groups(degree).size(); ++slot)
3950 {
3951 target_degrees.append(degree);
3952 target_slots.append(slot);
3953 }
3954
3955 if (target_degrees.is_empty())
3956 return result;
3957
3960 for (size_t t = 0; t < target_degrees.size(); ++t)
3961 {
3962 const size_t degree = target_degrees(t);
3964 for (size_t exp = 0; exp <= degree; ++exp)
3966 }
3967
3968 for (size_t flat = 0; flat < total_points; ++flat)
3969 {
3970 Array<Coefficient> coords = _decode_grid_point(grid, flat);
3971 Array<Coefficient> eval_pts = _build_eval_point(p.num_vars(), main_var, other_vars, coords);
3972 UniPoly u = p.homomorphic_eval(main_var, eval_pts);
3973 if (u.is_zero() or u.is_constant())
3975
3977 if (not _collect_sorted_primitive_factor_groups(u, p.degree_in(main_var), curr_groups))
3979
3980 for (size_t degree = 0; degree < base_groups.size(); ++degree)
3981 if (curr_groups(degree).size() != base_groups(degree).size())
3983
3984 for (size_t t = 0; t < target_degrees.size(); ++t)
3985 {
3986 const size_t degree = target_degrees(t);
3987 const size_t slot = target_slots(t);
3988 const UniPoly &match = curr_groups(degree)(slot);
3989
3990 for (size_t exp = 0; exp <= degree; ++exp)
3991 coeff_samples(t)(exp)(flat) = match.get_coeff(exp);
3992 }
3993 }
3994
3995 for (size_t t = 0; t < target_degrees.size(); ++t)
3996 {
3997 const size_t degree = target_degrees(t);
3998 Gen_MultiPolynomial candidate(p.num_vars());
3999 for (size_t exp = 0; exp <= degree; ++exp)
4000 {
4003 candidate
4004 += _embed_coeff_poly_as_x_term(coeff_poly, p.num_vars(), main_var, other_vars, exp);
4005 }
4006
4007 if (_divides_exactly(p, candidate))
4008 result.append(std::move(candidate));
4009 }
4010
4011 return result;
4012 }
4013
4016 Gen_MultiPolynomial &factor)
4017 requires std::is_integral_v<Coefficient>
4018 {
4019 for (size_t main_var = 0; main_var < p.num_vars(); ++main_var)
4020 {
4021 if (p.degree_in(main_var) == 0)
4022 continue;
4023
4024 for (Coefficient grid_start :
4027 {
4029 = _interpolated_factors_for_main_var(p, main_var, grid_start, grid_step);
4030 for (size_t i = 0; i < candidates.size(); ++i)
4031 if (not candidates(i).is_constant())
4032 {
4033 factor = std::move(candidates(i));
4034 return true;
4035 }
4036 }
4037 }
4038
4039 return false;
4040 }
4041
4049 size_t active_var)
4050 requires std::is_integral_v<Coefficient>
4051 {
4053
4054 UniPoly univ;
4055 p.for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
4056 {
4057 const size_t exp = idx(active_var);
4058 univ.set_coeff(exp, univ.get_coeff(exp) + c);
4059 });
4060
4061 DynList<FactorTerm> result;
4062 auto sfd_terms = univ.factorize();
4063 for (auto it = sfd_terms.get_it(); it.has_curr(); it.next_ne())
4064 {
4065 const auto &st = it.get_curr();
4066 result.append(
4067 FactorTerm{_embed_univariate(st.factor, p.num_vars(), active_var), st.multiplicity});
4068 }
4069 return result;
4070 }
4071
4089 {
4090 DynList<FactorTerm> result;
4091 const size_t k = candidates.size();
4092 if (k == 0)
4093 {
4094 if (not f.is_zero() and not f.is_constant())
4095 result.append(FactorTerm{f, 1});
4096 return result;
4097 }
4098
4099 if (k == 1)
4100 {
4101 if (not f.is_zero() and not f.is_constant())
4102 result.append(FactorTerm{f, 1});
4103 return result;
4104 }
4105
4106 // Track which candidates have been used
4107 Array<bool> used(k, false);
4108
4109 // Try subsets of increasing size (1, 2, ..., k/2)
4110 bool found_factor = true;
4111 while (found_factor)
4112 {
4113 found_factor = false;
4114
4115 // Count unused candidates
4116 size_t unused = 0;
4117 for (size_t i = 0; i < k; ++i)
4118 if (not used(i))
4119 ++unused;
4120
4121 if (unused <= 1)
4122 break;
4123
4124 // Try single candidates first (most common case)
4125 for (size_t i = 0; i < k; ++i)
4126 {
4127 if (used(i))
4128 continue;
4129
4130 const auto &cand = candidates(i);
4131 if (cand.is_zero() or cand.is_constant())
4132 {
4133 used(i) = true;
4134 continue;
4135 }
4136
4137 // Check if candidate divides f
4138 if (_divides_exactly(f, cand))
4139 {
4140 result.append(FactorTerm{cand, 1});
4141 f = _exact_quotient(f, cand);
4142 used(i) = true;
4143 found_factor = true;
4144
4145 if (f.is_constant())
4146 return result;
4147 break;
4148 }
4149 }
4150
4151 if (found_factor)
4152 continue;
4153
4154 // Try pairs of candidates
4155 for (size_t i = 0; i < k and not found_factor; ++i)
4156 {
4157 if (used(i))
4158 continue;
4159 for (size_t j = i + 1; j < k and not found_factor; ++j)
4160 {
4161 if (used(j))
4162 continue;
4163
4165 if (_divides_exactly(f, product))
4166 {
4167 result.append(FactorTerm{product, 1});
4168 f = _exact_quotient(f, product);
4169 used(i) = true;
4170 used(j) = true;
4171 found_factor = true;
4172
4173 if (f.is_constant())
4174 return result;
4175 }
4176 }
4177 }
4178 }
4179
4180 // Whatever remains of f is a factor
4181 if (not f.is_zero() and not f.is_constant())
4182 result.append(FactorTerm{f, 1});
4183
4184 return result;
4185 }
4186
4219 {
4220 DynList<FactorTerm> result;
4221
4222 if (is_zero() or is_constant())
4223 return result;
4224
4225 // Step 1: Extract content and compute primitive part
4226 const Coefficient scalar_content = content();
4227 Gen_MultiPolynomial remaining = primitive_part();
4228
4229 if (remaining.is_zero() or remaining.is_constant())
4230 return result;
4231
4232 if (scalar_content != Coefficient(1))
4234
4235 size_t active_var = 0;
4236 size_t active_vars = _count_active_vars(remaining, active_var);
4237
4238 // Special case: effectively univariate even if embedded in a larger ring.
4239 if (active_vars == 1)
4240 {
4241 DynList<FactorTerm> tail = _factorize_as_univariate_in_var(remaining, active_var);
4242 for (auto it = tail.get_it(); it.has_curr(); it.next_ne())
4243 result.append(it.get_curr());
4244 return result;
4245 }
4246
4247 // Step 2: Repeatedly lift exact factors from specializations when possible.
4248 while (true)
4249 {
4251 if (not _try_extract_affine_linear_factor(remaining, lifted_factor)
4252 and not _try_extract_interpolated_factor(remaining, lifted_factor))
4253 break;
4254
4255 size_t multiplicity = 1;
4256 remaining = _exact_quotient(remaining, lifted_factor);
4257 while (not remaining.is_zero() and not remaining.is_constant()
4258 and _divides_exactly(remaining, lifted_factor))
4259 {
4260 remaining = _exact_quotient(remaining, lifted_factor);
4261 ++multiplicity;
4262 }
4263
4264 result.append(FactorTerm{lifted_factor, multiplicity});
4265
4266 if (remaining.is_zero() or remaining.is_constant())
4267 return result;
4268
4269 active_vars = _count_active_vars(remaining, active_var);
4270 if (active_vars == 1)
4271 {
4272 DynList<FactorTerm> tail = _factorize_as_univariate_in_var(remaining, active_var);
4273 for (auto it = tail.get_it(); it.has_curr(); it.next_ne())
4274 result.append(it.get_curr());
4275 return result;
4276 }
4277 }
4278
4280
4281 // Step 3: Choose main variable among variables that actually appear.
4282 size_t main_var = nvars_;
4283 size_t min_deg = std::numeric_limits<size_t>::max();
4284 for (size_t v = 0; v < nvars_; ++v)
4285 {
4286 size_t dv = remaining.degree_in(v);
4287 if (dv == 0)
4288 continue;
4289
4290 if (dv < min_deg)
4291 {
4292 min_deg = dv;
4293 main_var = v;
4294 }
4295 }
4296
4297 ah_domain_error_if(main_var >= nvars_) << "factorize: primitive part has no active variable";
4298
4299 // Step 3: Choose evaluation points (small integers, avoiding roots)
4301 {
4302 Coefficient val(0);
4303 size_t pt_idx = 0;
4304 for (size_t v = 0; v < nvars_; ++v)
4305 {
4306 if (v == main_var)
4307 continue;
4308 eval_pts(pt_idx) = val;
4309 val = val + Coefficient(1);
4310 ++pt_idx;
4311 }
4312 }
4313
4314 // Step 4: Homomorphic evaluation to get univariate polynomial
4315 UniPoly g = remaining.homomorphic_eval(main_var, eval_pts);
4316
4317 if (g.is_zero() or g.is_constant())
4318 {
4319 // Evaluation collapsed; try different points
4320 {
4321 Coefficient val(1);
4322 size_t pt_idx = 0;
4323 for (size_t v = 0; v < nvars_; ++v)
4324 {
4325 if (v == main_var)
4326 continue;
4327 eval_pts(pt_idx) = val;
4328 val = val + Coefficient(2);
4329 ++pt_idx;
4330 }
4331 }
4332 g = remaining.homomorphic_eval(main_var, eval_pts);
4333
4334 if (g.is_zero() or g.is_constant())
4335 {
4336 // Still collapsed; return as irreducible
4337 result.append(FactorTerm{remaining, 1});
4338 return result;
4339 }
4340 }
4341
4342 // Step 5: Factor the univariate polynomial
4343 auto univ_sfd = g.factorize();
4344
4345 // Collect all factors, preserving multiplicity from the specialized image.
4347 for (auto it = univ_sfd.get_it(); it.has_curr(); it.next_ne())
4348 {
4349 const auto &term = it.get_curr();
4350 for (size_t m = 0; m < term.multiplicity; ++m)
4351 univ_factors.append(term.factor);
4352 }
4353
4354 if (_list_size(univ_factors) <= 1)
4355 {
4356 // Univariate is irreducible; original is likely irreducible
4357 result.append(FactorTerm{remaining, 1});
4358 return result;
4359 }
4360
4361 // Step 6: Embed univariate factors and try recombination
4362 size_t n_univ = _list_size(univ_factors);
4364 {
4365 size_t i = 0;
4366 for (auto it = univ_factors.get_it(); it.has_curr(); it.next_ne())
4367 candidates(i++) = _embed_univariate(it.get_curr(), nvars_, main_var);
4368 }
4369
4370 // Step 7: Factor recombination by trial division
4371 DynList<FactorTerm> tail = factor_recombination(remaining, candidates);
4372 for (auto it = tail.get_it(); it.has_curr(); it.next_ne())
4373 result.append(it.get_curr());
4374
4375 return result;
4376 }
4377
4378private:
4381 {
4382 Coefficient v = Coefficient(1);
4383 for (size_t j = 0; j < alpha.size(); ++j)
4384 if (alpha(j) != 0)
4385 v *= multi_poly_detail::int_power(pt(j), alpha(j));
4386 return v;
4387 }
4388
4389 // -----------------------------------------------------------------
4390 // Layer 6 private helpers
4391 // -----------------------------------------------------------------
4392
4395 size_t var,
4396 const Coefficient &val)
4397 {
4398 Gen_MultiPolynomial result(p.nvars_);
4399 p.for_each_term([&](const Array<size_t> &idx, const Coefficient &c)
4400 {
4401 Coefficient coeff = c;
4402 if (idx(var) != 0)
4403 coeff *= multi_poly_detail::int_power(val, idx(var));
4404
4405 Array<size_t> new_idx = idx;
4406 new_idx(var) = 0;
4407 result.add_to_coeff(new_idx, coeff);
4408 });
4409 return result;
4410 }
4411
4413 template <typename T>
4414 static size_t _list_size(const DynList<T> &lst)
4415 {
4416 size_t n = 0;
4417 for (auto it = lst.get_it(); it.has_curr(); it.next_ne())
4418 ++n;
4419 return n;
4420 }
4421
4428 {
4429 if (divisor.is_zero())
4430 return false;
4431 if (f.is_zero())
4432 return true;
4433 if (divisor.degree() > f.degree())
4434 return false;
4435
4437 auto [q, r] = f.divmod(divisors);
4438 (void) q;
4439 return r.is_zero();
4440 }
4441
4449 {
4450 if (divisor.is_zero())
4451 return Gen_MultiPolynomial(f.num_vars());
4452
4454 auto [q, r] = f.divmod(divisors);
4455 ah_domain_error_if(not r.is_zero())
4456 << "_exact_quotient: divisor does not divide polynomial exactly";
4457 return q(0);
4458 }
4459
4460}; // class Gen_MultiPolynomial
4461
4462// ===================================================================
4463// Residual Analysis Helpers
4464// ===================================================================
4465
4476template <typename C>
4478{
4479 double r_squared = 0.0;
4480 double rmse = 0.0;
4481 double rss = 0.0;
4482 double tss = 0.0;
4483 double ess = 0.0;
4485 double mean_y = 0.0;
4486};
4487
4496template <typename C, class M>
4498 const Array<std::pair<Array<C>, C>> &data)
4499{
4500 PolyFitAnalysis<C> result;
4501 const size_t m = data.size();
4502
4503 if (m == 0)
4504 return result;
4505
4506 // Compute mean of y
4507 C sum_y = C{};
4508 for (size_t i = 0; i < m; ++i)
4509 sum_y += data(i).second;
4510 result.mean_y = static_cast<double>(sum_y) / static_cast<double>(m);
4511
4512 // Compute residuals, TSS, RSS
4513 result.residuals = Array<C>(m, C{});
4514 result.tss = 0.0;
4515 result.rss = 0.0;
4516
4517 for (size_t i = 0; i < m; ++i)
4518 {
4519 C pred = poly.eval(data(i).first);
4520 C res = data(i).second - pred;
4521 result.residuals(i) = res;
4522
4523 const auto dev_y = static_cast<double>(data(i).second) - result.mean_y;
4524 result.tss += dev_y * dev_y;
4525
4526 const auto res_d = static_cast<double>(res);
4527 result.rss += res_d * res_d;
4528 }
4529
4530 result.ess = result.tss - result.rss;
4531
4532 // Compute R²
4533 if (result.tss > 1e-16)
4534 result.r_squared = result.ess / result.tss;
4535 else
4536 result.r_squared = 0.0;
4537
4538 // Compute RMSE
4539 result.rmse = std::sqrt(result.rss / static_cast<double>(m));
4540
4541 return result;
4542}
4543
4544// ===================================================================
4545// Free operators
4546// ===================================================================
4547
4553template <typename C, class M>
4555{
4556 return p * s;
4557}
4558
4564template <typename C, class M>
4566{
4567 return p + s;
4568}
4569
4575template <typename C, class M>
4580
4586template <typename C, class M>
4587std::ostream &operator<<(std::ostream &out, const Gen_MultiPolynomial<C, M> &p)
4588{
4589 return out << p.to_str();
4590}
4591
4592// ===================================================================
4593// Convenient typedefs
4594// ===================================================================
4595
4598
4601
4604
4607
4610
4611} // namespace Aleph
4612
4613#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:527
#define ah_invalid_argument_if(C)
Throws std::invalid_argument if condition holds.
Definition ah-errors.H:644
Parallel functional programming operations using ThreadPool.
High-level sorting functions for Aleph containers.
size_t size_t int32_t value
Definition ca-c-api.h:116
size_t size_t int32_t * out
Definition ca-c-api.h:120
size_t row
Definition ca-c-api.h:115
size_t * rows
Definition ca-c-api.h:112
size_t size_t col
Definition ca-c-api.h:116
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:365
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:250
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:320
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.
Doubly-linked list (defined in tpl_dynList.H).
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.
size_t degree() const noexcept
Degree of the polynomial.
Coefficient leading_coeff() const noexcept
Leading coefficient (of the highest-degree term).
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:429
void for_each(Operation &operation)
Traverse all the container and performs an operation on each element.
Definition ah-dry.H:796
constexpr bool is_empty() const noexcept
Checks if the graph is empty (has no nodes).
Definition graph-dry.H:743
auto get_it() const
Return a properly initialized iterator positioned at the first item on the container.
Definition ah-dry.H:228
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:4077
__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:4063
__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:4126
__gmp_expr< T, __gmp_unary_expr< __gmp_expr< T, U >, __gmp_gamma_function > > gamma(const __gmp_expr< T, U > &expr)
Definition gmpfrxx.h:4103
int cmp(const __gmp_expr< T, U > &expr1, const __gmp_expr< V, W > &expr2)
Definition gmpfrxx.h:4129
List_Graph< Graph_Node< string >, Graph_Arc< Empty_Class > > G
DynArray< Graph::Node * > nodes
Definition graphpic.C:406
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
Singly linked list implementations with head-tail access.
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
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)
Return intersection of two containers as a DynList.
size_t size(Node *root) noexcept
and
Check uniqueness with explicit hash + equality functors.
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:328
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 a virtual destructor.
Definition tpl_avl.H:743
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.