Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
multi_polynomial_test.cc
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 MIT License (see tpl_multi_polynomial.H for full text)
7*/
8
9#include <gtest/gtest.h>
10#include <chrono>
11#include <concepts>
12#include <cmath>
13#include <string>
14#include <sstream>
15#include <stdexcept>
16#include <random>
18
19using namespace Aleph;
20
21// Shorthand for multi-indices
23
24template <typename MP>
25concept HasSPoly = requires(const MP &f, const MP &g)
26{
27 { MP::s_poly(f, g) } -> std::same_as<MP>;
28};
29
30template <typename MP>
31concept HasGroebnerBasis = requires(const Array<MP> &gens)
32{
33 { MP::groebner_basis(gens) } -> std::same_as<Array<MP>>;
34};
35
36template <typename MP>
37concept HasIdealMember = requires(const MP &f, const Array<MP> &gens)
38{
39 { MP::ideal_member(f, gens) } -> std::same_as<bool>;
40};
41
42template <typename MP>
43concept HasRadicalMember = requires(const MP &f, const Array<MP> &gens)
44{
45 { MP::radical_member(f, gens) } -> std::same_as<bool>;
46};
47
48static_assert(HasSPoly<MultiPolynomial>);
52
54
55template <typename MP>
57{
58 for (size_t i = 0; i < basis.size(); ++i)
59 for (size_t j = i + 1; j < basis.size(); ++j)
60 {
61 MP s = MP::s_poly(basis(i), basis(j));
62 auto [q, r] = s.divmod(basis);
63 (void) q;
64 EXPECT_TRUE(r.is_zero())
65 << "Non-zero S-polynomial remainder for pair (" << i << ", " << j << ")";
66 }
67}
68
69template <typename MP>
71{
72 for (size_t i = 0; i < basis.size(); ++i)
73 {
75 for (size_t j = 0; j < basis.size(); ++j)
76 if (i != j)
78
79 if (others.is_empty())
80 continue;
81
82 auto [q, r] = basis(i).divmod(others);
83 (void) q;
84 EXPECT_TRUE((r - basis(i)).is_zero())
85 << "Basis element " << i << " is still reducible by the rest of the basis";
86 }
87}
88
89static_assert(not HasSPoly<IntGrevlexPoly>);
93
94// ===================================================================
95// Construction
96// ===================================================================
97
99{
101 EXPECT_TRUE(p.is_zero());
102 EXPECT_EQ(p.num_vars(), 0u);
103 EXPECT_EQ(p.num_terms(), 0u);
104 EXPECT_EQ(p.degree(), 0u);
105}
106
108{
109 MultiPolynomial p(2, 5.0);
112 EXPECT_EQ(p.num_vars(), 2u);
113 EXPECT_EQ(p.num_terms(), 1u);
114 EXPECT_EQ(p.degree(), 0u);
115 EXPECT_DOUBLE_EQ(p.coeff_at(Idx{0, 0}), 5.0);
116}
117
119{
120 MultiPolynomial p(3, 0.0);
121 EXPECT_TRUE(p.is_zero());
122 EXPECT_EQ(p.num_vars(), 3u);
123 EXPECT_EQ(p.num_terms(), 0u);
124}
125
127{
128 // 3 x^2 y
129 MultiPolynomial p(2, Idx{2, 1}, 3.0);
130 EXPECT_FALSE(p.is_zero());
131 EXPECT_FALSE(p.is_constant());
132 EXPECT_EQ(p.num_terms(), 1u);
133 EXPECT_EQ(p.degree(), 3u);
134 EXPECT_DOUBLE_EQ(p.coeff_at(Idx{2, 1}), 3.0);
135 EXPECT_DOUBLE_EQ(p.coeff_at(Idx{0, 0}), 0.0);
136}
137
139{
140 // f(x,y) = 3x^2*y + 2x - 1
141 MultiPolynomial f(2, {
142 {Idx{2, 1}, 3.0},
143 {Idx{1, 0}, 2.0},
144 {Idx{0, 0}, -1.0},
145 });
146
147 EXPECT_EQ(f.num_vars(), 2u);
148 EXPECT_EQ(f.num_terms(), 3u);
149 EXPECT_EQ(f.degree(), 3u);
150 EXPECT_DOUBLE_EQ(f.coeff_at(Idx{2, 1}), 3.0);
151 EXPECT_DOUBLE_EQ(f.coeff_at(Idx{1, 0}), 2.0);
152 EXPECT_DOUBLE_EQ(f.coeff_at(Idx{0, 0}), -1.0);
153}
154
156{
157 MultiPolynomial f(1, {
158 {Idx{2}, 3.0},
159 {Idx{2}, 4.0},
160 {Idx{0}, 1.0},
161 });
162
163 EXPECT_EQ(f.num_terms(), 2u);
164 EXPECT_DOUBLE_EQ(f.coeff_at(Idx{2}), 7.0);
165 EXPECT_DOUBLE_EQ(f.coeff_at(Idx{0}), 1.0);
166}
167
169{
170 MultiPolynomial f(1, {
171 {Idx{2}, 3.0},
172 {Idx{2}, -3.0},
173 });
174
175 EXPECT_TRUE(f.is_zero());
176}
177
178// ===================================================================
179// Factory helpers
180// ===================================================================
181
183{
184 auto x = MultiPolynomial::variable(2, 0);
185 auto y = MultiPolynomial::variable(2, 1);
186
187 EXPECT_EQ(x.num_vars(), 2u);
188 EXPECT_EQ(x.degree(), 1u);
189 EXPECT_DOUBLE_EQ(x.coeff_at(Idx{1, 0}), 1.0);
190
191 EXPECT_EQ(y.num_vars(), 2u);
192 EXPECT_EQ(y.degree(), 1u);
193 EXPECT_DOUBLE_EQ(y.coeff_at(Idx{0, 1}), 1.0);
194}
195
197{
198 EXPECT_THROW(MultiPolynomial::variable(2, 2), std::domain_error);
199}
200
202{
203 auto m = MultiPolynomial::monomial(3, Idx{1, 2, 0}, 5.0);
204 EXPECT_EQ(m.num_vars(), 3u);
205 EXPECT_EQ(m.degree(), 3u);
206 EXPECT_DOUBLE_EQ(m.coeff_at(Idx{1, 2, 0}), 5.0);
207}
208
209// ===================================================================
210// Properties
211// ===================================================================
212
214{
215 // f(x,y,z) = x^3*z + y^2*z^4
216 MultiPolynomial f(3, {
217 {Idx{3, 0, 1}, 1.0},
218 {Idx{0, 2, 4}, 1.0},
219 });
220
221 EXPECT_EQ(f.degree(), 6u); // max(3+0+1, 0+2+4) = 6
222 EXPECT_EQ(f.degree_in(0), 3u);
223 EXPECT_EQ(f.degree_in(1), 2u);
224 EXPECT_EQ(f.degree_in(2), 4u);
225}
226
228{
229 // f = x^2 + xy + y^2 (grevlex: x^2 > xy > y^2)
230 MultiPolynomial f(2, {
231 {Idx{2, 0}, 1.0},
232 {Idx{1, 1}, 1.0},
233 {Idx{0, 2}, 1.0},
234 });
235
236 auto [lm, lc] = f.leading_term();
237 EXPECT_DOUBLE_EQ(lc, 1.0);
238 EXPECT_EQ(lm(0), 2u);
239 EXPECT_EQ(lm(1), 0u);
240}
241
243{
244 // Same polynomial with lex ordering
246 {Idx{2, 0}, 1.0},
247 {Idx{1, 1}, 1.0},
248 {Idx{0, 2}, 1.0},
249 });
250
251 auto [lm, lc] = f.leading_term();
252 EXPECT_DOUBLE_EQ(lc, 1.0);
253 EXPECT_EQ(lm(0), 2u);
254 EXPECT_EQ(lm(1), 0u);
255}
256
258{
260 EXPECT_THROW(z.leading_term(), std::domain_error);
261}
262
263// ===================================================================
264// Arithmetic — addition & subtraction
265// ===================================================================
266
268{
269 MultiPolynomial a(2, {
270 {Idx{2, 0}, 3.0},
271 {Idx{0, 1}, 1.0},
272 });
273
274 MultiPolynomial b(2, {
275 {Idx{2, 0}, -1.0},
276 {Idx{1, 0}, 2.0},
277 });
278
279 MultiPolynomial c = a + b;
280 EXPECT_EQ(c.num_terms(), 3u);
281 EXPECT_DOUBLE_EQ(c.coeff_at(Idx{2, 0}), 2.0);
282 EXPECT_DOUBLE_EQ(c.coeff_at(Idx{1, 0}), 2.0);
283 EXPECT_DOUBLE_EQ(c.coeff_at(Idx{0, 1}), 1.0);
284}
285
287{
288 auto x = MultiPolynomial::variable(2, 0);
289 auto y = MultiPolynomial::variable(2, 1);
290 MultiPolynomial p = x + y;
291 MultiPolynomial q = p + p;
292
293 EXPECT_EQ(q, 2.0 * p);
294}
295
297{
298 auto x = MultiPolynomial::variable(2, 0);
299 MultiPolynomial p = x + 2.0;
300
301 EXPECT_EQ(p.num_vars(), 2u);
302 EXPECT_EQ(p.num_terms(), 2u);
303 EXPECT_DOUBLE_EQ(p.coeff_at(Idx{1, 0}), 1.0);
304 EXPECT_DOUBLE_EQ(p.coeff_at(Idx{0, 0}), 2.0);
305}
306
308{
309 auto x = MultiPolynomial::variable(2, 0);
310 MultiPolynomial p = 2.0 + x;
311
312 EXPECT_EQ(p.num_vars(), 2u);
313 EXPECT_EQ(p.num_terms(), 2u);
314 EXPECT_DOUBLE_EQ(p.coeff_at(Idx{1, 0}), 1.0);
315 EXPECT_DOUBLE_EQ(p.coeff_at(Idx{0, 0}), 2.0);
316}
317
319{
320 auto x = MultiPolynomial::variable(2, 0);
321 MultiPolynomial z = x - x;
322 EXPECT_TRUE(z.is_zero());
323}
324
326{
327 MultiPolynomial p(2, {
328 {Idx{3, 1}, 7.0},
329 {Idx{0, 0}, 2.0},
330 });
331 p -= p;
332 EXPECT_TRUE(p.is_zero());
333}
334
336{
337 auto y = MultiPolynomial::variable(2, 1);
338 MultiPolynomial p = y - 3.0;
339
340 EXPECT_EQ(p.num_vars(), 2u);
341 EXPECT_EQ(p.num_terms(), 2u);
342 EXPECT_DOUBLE_EQ(p.coeff_at(Idx{0, 1}), 1.0);
343 EXPECT_DOUBLE_EQ(p.coeff_at(Idx{0, 0}), -3.0);
344}
345
347{
348 auto x = MultiPolynomial::variable(2, 0);
349 MultiPolynomial p = 1.0 - x;
350
351 EXPECT_EQ(p.num_vars(), 2u);
352 EXPECT_EQ(p.num_terms(), 2u);
353 EXPECT_DOUBLE_EQ(p.coeff_at(Idx{1, 0}), -1.0);
354 EXPECT_DOUBLE_EQ(p.coeff_at(Idx{0, 0}), 1.0);
355}
356
358{
359 MultiPolynomial p(2, {
360 {Idx{1, 0}, 3.0},
361 {Idx{0, 1}, -2.0},
362 });
363
364 MultiPolynomial q = -p;
365 EXPECT_DOUBLE_EQ(q.coeff_at(Idx{1, 0}), -3.0);
366 EXPECT_DOUBLE_EQ(q.coeff_at(Idx{0, 1}), 2.0);
367}
368
369// ===================================================================
370// Arithmetic — scalar operations
371// ===================================================================
372
374{
375 MultiPolynomial p(2, {
376 {Idx{1, 0}, 3.0},
377 {Idx{0, 0}, 1.0},
378 });
379
380 MultiPolynomial q = p * 2.0;
381 EXPECT_DOUBLE_EQ(q.coeff_at(Idx{1, 0}), 6.0);
382 EXPECT_DOUBLE_EQ(q.coeff_at(Idx{0, 0}), 2.0);
383
384 // Commutative: s * p == p * s
385 EXPECT_EQ(2.0 * p, p * 2.0);
386}
387
389{
390 MultiPolynomial p(2, Idx{1, 0}, 5.0);
391 MultiPolynomial q = p * 0.0;
392 EXPECT_TRUE(q.is_zero());
393}
394
396{
397 MultiPolynomial p(2, {
398 {Idx{1, 0}, 6.0},
399 {Idx{0, 1}, 4.0},
400 });
401
402 MultiPolynomial q = p / 2.0;
403 EXPECT_DOUBLE_EQ(q.coeff_at(Idx{1, 0}), 3.0);
404 EXPECT_DOUBLE_EQ(q.coeff_at(Idx{0, 1}), 2.0);
405}
406
408{
409 MultiPolynomial p(2, Idx{0, 0}, 1.0);
410 EXPECT_THROW(p / 0.0, std::domain_error);
411}
412
413// ===================================================================
414// Arithmetic — polynomial multiplication
415// ===================================================================
416
418{
419 // (x + y) * (x - y) = x^2 - y^2
420 auto x = MultiPolynomial::variable(2, 0);
421 auto y = MultiPolynomial::variable(2, 1);
422
423 MultiPolynomial a = x + y;
424 MultiPolynomial b = x - y;
425 MultiPolynomial c = a * b;
426
427 EXPECT_EQ(c.num_terms(), 2u);
428 EXPECT_DOUBLE_EQ(c.coeff_at(Idx{2, 0}), 1.0);
429 EXPECT_DOUBLE_EQ(c.coeff_at(Idx{0, 2}), -1.0);
430 EXPECT_DOUBLE_EQ(c.coeff_at(Idx{1, 1}), 0.0);
431}
432
434{
435 // (x + y)^2 = x^2 + 2xy + y^2
436 auto x = MultiPolynomial::variable(2, 0);
437 auto y = MultiPolynomial::variable(2, 1);
438
439 MultiPolynomial sq = (x + y) * (x + y);
440 EXPECT_EQ(sq.num_terms(), 3u);
441 EXPECT_DOUBLE_EQ(sq.coeff_at(Idx{2, 0}), 1.0);
442 EXPECT_DOUBLE_EQ(sq.coeff_at(Idx{1, 1}), 2.0);
443 EXPECT_DOUBLE_EQ(sq.coeff_at(Idx{0, 2}), 1.0);
444}
445
447{
448 auto x = MultiPolynomial::variable(2, 0);
449 auto y = MultiPolynomial::variable(2, 1);
450
451 MultiPolynomial a = x * x + y; // x^2 + y
452 MultiPolynomial b = x + MultiPolynomial(2, 1.0); // x + 1
453 MultiPolynomial c = y * y; // y^2
454
455 // a * (b + c) == a*b + a*c
456 EXPECT_EQ(a * (b + c), a * b + a * c);
457}
458
460{
461 auto x = MultiPolynomial::variable(2, 0);
462 auto y = MultiPolynomial::variable(2, 1);
463
464 MultiPolynomial a = x * x + y + MultiPolynomial(2, 3.0);
465 MultiPolynomial b = x * y + MultiPolynomial(2, -2.0);
466
467 EXPECT_EQ(a * b, b * a);
468}
469
471{
472 auto x = MultiPolynomial::variable(2, 0);
473 MultiPolynomial z(2);
474 EXPECT_TRUE((x * z).is_zero());
475}
476
478{
479 auto x = MultiPolynomial::variable(2, 0);
480 auto y = MultiPolynomial::variable(2, 1);
481 MultiPolynomial p = x + y;
482 MultiPolynomial one(2, 1.0);
483 EXPECT_EQ(p * one, p);
484}
485
486// ===================================================================
487// Exponentiation
488// ===================================================================
489
491{
492 auto x = MultiPolynomial::variable(2, 0);
493 MultiPolynomial p = x.pow(0);
495 EXPECT_DOUBLE_EQ(p.coeff_at(Idx{0, 0}), 1.0);
496}
497
499{
500 auto x = MultiPolynomial::variable(2, 0);
501 auto y = MultiPolynomial::variable(2, 1);
502 MultiPolynomial p = x + y;
503 EXPECT_EQ(p.pow(1), p);
504}
505
507{
508 auto x = MultiPolynomial::variable(2, 0);
509 auto y = MultiPolynomial::variable(2, 1);
510 MultiPolynomial p = x + y;
511 EXPECT_EQ(p.pow(2), p * p);
512}
513
515{
516 auto x = MultiPolynomial::variable(2, 0);
517 auto y = MultiPolynomial::variable(2, 1);
518 MultiPolynomial p = x + y;
519 EXPECT_EQ(p.pow(3), p * p * p);
520}
521
522// ===================================================================
523// Evaluation
524// ===================================================================
525
527{
528 MultiPolynomial p(2, 7.0);
529 Array<double> pt{1.0, 2.0};
530 EXPECT_DOUBLE_EQ(p.eval(pt), 7.0);
531}
532
534{
535 // f(x,y) = 3x + 2y + 1
536 MultiPolynomial f(2, {
537 {Idx{1, 0}, 3.0},
538 {Idx{0, 1}, 2.0},
539 {Idx{0, 0}, 1.0},
540 });
541
542 Array<double> pt{2.0, 3.0};
543 // 3*2 + 2*3 + 1 = 13
544 EXPECT_DOUBLE_EQ(f.eval(pt), 13.0);
545}
546
548{
549 // f(x,y) = x^2 + 2xy + y^2 = (x+y)^2
550 auto x = MultiPolynomial::variable(2, 0);
551 auto y = MultiPolynomial::variable(2, 1);
552 MultiPolynomial f = (x + y).pow(2);
553
554 Array<double> pt{3.0, 4.0};
555 EXPECT_DOUBLE_EQ(f.eval(pt), 49.0); // (3+4)^2 = 49
556}
557
559{
560 // f(x,y,z) = x*y*z
561 MultiPolynomial f(3, Idx{1, 1, 1}, 1.0);
562 Array<double> pt{2.0, 3.0, 5.0};
563 EXPECT_DOUBLE_EQ(f.eval(pt), 30.0);
564}
565
567{
568 MultiPolynomial f(1, Idx{2}, 1.0); // x^2
569 Array<double> pt{5.0};
570 EXPECT_DOUBLE_EQ(f(pt), 25.0);
571}
572
574{
575 MultiPolynomial f(3, Idx{1, 1, 1}, 1.0);
576 Array<double> pt{1.0, 2.0}; // only 2, need 3
577 EXPECT_THROW(f.eval(pt), std::domain_error);
578}
579
581{
582 MultiPolynomial z(2);
583 Array<double> pt{1.0, 2.0};
584 EXPECT_DOUBLE_EQ(z.eval(pt), 0.0);
585}
586
587// ===================================================================
588// Comparison
589// ===================================================================
590
592{
593 auto x = MultiPolynomial::variable(2, 0);
594 auto y = MultiPolynomial::variable(2, 1);
595
596 MultiPolynomial a = x + y;
597 MultiPolynomial b = y + x;
598
599 EXPECT_EQ(a, b);
600}
601
603{
604 auto x = MultiPolynomial::variable(2, 0);
605 auto y = MultiPolynomial::variable(2, 1);
606
607 EXPECT_NE(x, y);
608}
609
616
617// ===================================================================
618// Monomial orderings
619// ===================================================================
620
622{
623 // In lex: x > y^100 (because x has higher first exponent)
624 // In grevlex: y^100 > x (because total degree 100 > 1)
626 {Idx{1, 0}, 1.0},
627 {Idx{0, 100}, 1.0},
628 });
629
631 {Idx{1, 0}, 1.0},
632 {Idx{0, 100}, 1.0},
633 });
634
635 // Leading term in lex: x = [1,0]
636 auto [lex_lm, lex_lc] = lex_p.leading_term();
637 EXPECT_EQ(lex_lm(0), 1u);
638 EXPECT_EQ(lex_lm(1), 0u);
639
640 // Leading term in grevlex: y^100 = [0,100]
641 auto [grev_lm, grev_lc] = grev_p.leading_term();
642 EXPECT_EQ(grev_lm(0), 0u);
643 EXPECT_EQ(grev_lm(1), 100u);
644}
645
647{
648 // In grlex with same total degree: lex decides.
649 // x^2*y vs x*y^2: total degree 3 both.
650 // Lex: [2,1] > [1,2] (leftmost diff: 2 > 1)
652 {Idx{2, 1}, 1.0},
653 {Idx{1, 2}, 1.0},
654 });
655
656 auto [lm, lc] = p.leading_term();
657 EXPECT_EQ(lm(0), 2u);
658 EXPECT_EQ(lm(1), 1u);
659}
660
662{
663 // Grevlex: same total degree → larger rightmost exp is SMALLER.
664 // [2,1] vs [1,2]: rightmost nonzero of diff = [1,-1] is -1 (negative).
665 // So [2,1] >_grevlex [1,2]. Leading term is [2,1].
666 MultiPolynomial p(2, {
667 {Idx{2, 1}, 1.0},
668 {Idx{1, 2}, 1.0},
669 });
670
671 auto [lm, lc] = p.leading_term();
672 EXPECT_EQ(lm(0), 2u);
673 EXPECT_EQ(lm(1), 1u);
674}
675
677{
678 // With 3 vars, grevlex differs from grlex.
679 // x*z vs y^2: total degree 2 both.
680 // x*z = [1,0,1], y^2 = [0,2,0]
681 // diff = [1,-2,1]. Rightmost nonzero = 1 (positive at index 2).
682 // So [1,0,1] <_grevlex [0,2,0] (positive rightmost → [1,0,1] is smaller)
683 // Leading term in grevlex: y^2
684 MultiPolynomial p(3, {
685 {Idx{1, 0, 1}, 1.0},
686 {Idx{0, 2, 0}, 1.0},
687 });
688
689 auto [lm, lc] = p.leading_term();
690 EXPECT_EQ(lm(0), 0u);
691 EXPECT_EQ(lm(1), 2u);
692 EXPECT_EQ(lm(2), 0u);
693
694 // In grlex, same total degree → lex decides.
695 // [1,0,1] vs [0,2,0] in lex: 1 > 0 at index 0, so [1,0,1] >_lex [0,2,0]
696 // Leading in grlex: x*z
698 {Idx{1, 0, 1}, 1.0},
699 {Idx{0, 2, 0}, 1.0},
700 });
701
702 auto [qlm, qlc] = q.leading_term();
703 EXPECT_EQ(qlm(0), 1u);
704 EXPECT_EQ(qlm(1), 0u);
705 EXPECT_EQ(qlm(2), 1u);
706}
707
708// ===================================================================
709// String representation
710// ===================================================================
711
713{
715 EXPECT_EQ(z.to_str(), "0");
716}
717
719{
720 MultiPolynomial c(2, 5.0);
721 EXPECT_EQ(c.to_str(), "5");
722}
723
725{
726 auto x = MultiPolynomial::variable(2, 0);
727 EXPECT_EQ(x.to_str(), "x");
728}
729
731{
732 MultiPolynomial p(2, Idx{1, 0}, -3.0);
733 EXPECT_EQ(p.to_str(), "-3*x");
734}
735
737{
738 auto x = MultiPolynomial::variable(1, 0);
739 std::ostringstream oss;
740 oss << x;
741 EXPECT_EQ(oss.str(), "x");
742}
743
744// ===================================================================
745// Iteration
746// ===================================================================
747
749{
750 MultiPolynomial f(2, {
751 {Idx{1, 0}, 3.0},
752 {Idx{0, 1}, 2.0},
753 });
754
755 size_t count = 0;
756 f.for_each_term([&](const Idx &, double) { ++count; });
757 EXPECT_EQ(count, 2u);
758}
759
761{
762 auto x = MultiPolynomial::variable(2, 0);
763 auto y = MultiPolynomial::variable(2, 1);
764 MultiPolynomial p = x + y;
765
766 auto t = p.terms();
767 EXPECT_EQ(t.size(), 2u);
768}
769
770// ===================================================================
771// Promote
772// ===================================================================
773
775{
776 // f(x,y) = x + y, promote to 3 vars
777 auto x = MultiPolynomial::variable(2, 0);
778 auto y = MultiPolynomial::variable(2, 1);
779 MultiPolynomial p = x + y;
780
781 MultiPolynomial q = p.promote(3);
782 EXPECT_EQ(q.num_vars(), 3u);
783 EXPECT_EQ(q.num_terms(), 2u);
784 EXPECT_DOUBLE_EQ(q.coeff_at(Idx{1, 0, 0}), 1.0);
785 EXPECT_DOUBLE_EQ(q.coeff_at(Idx{0, 1, 0}), 1.0);
786}
787
789{
790 auto x = MultiPolynomial::variable(2, 0);
791 EXPECT_EQ(x.promote(2), x);
792}
793
795{
796 auto x = MultiPolynomial::variable(3, 0);
797 EXPECT_THROW(x.promote(2), std::domain_error);
798}
799
800// ===================================================================
801// Algebraic identities
802// ===================================================================
803
805{
806 // (x + y + z)^2 = x^2 + y^2 + z^2 + 2xy + 2xz + 2yz
807 auto x = MultiPolynomial::variable(3, 0);
808 auto y = MultiPolynomial::variable(3, 1);
809 auto z = MultiPolynomial::variable(3, 2);
810
811 MultiPolynomial sq = (x + y + z).pow(2);
812
813 EXPECT_EQ(sq.num_terms(), 6u);
814 EXPECT_EQ(sq.degree(), 2u);
815 EXPECT_DOUBLE_EQ(sq.coeff_at(Idx{2, 0, 0}), 1.0);
816 EXPECT_DOUBLE_EQ(sq.coeff_at(Idx{0, 2, 0}), 1.0);
817 EXPECT_DOUBLE_EQ(sq.coeff_at(Idx{0, 0, 2}), 1.0);
818 EXPECT_DOUBLE_EQ(sq.coeff_at(Idx{1, 1, 0}), 2.0);
819 EXPECT_DOUBLE_EQ(sq.coeff_at(Idx{1, 0, 1}), 2.0);
820 EXPECT_DOUBLE_EQ(sq.coeff_at(Idx{0, 1, 1}), 2.0);
821}
822
824{
825 auto x = MultiPolynomial::variable(3, 0);
826 auto y = MultiPolynomial::variable(3, 1);
827 auto z = MultiPolynomial::variable(3, 2);
828
829 // (x + y)(x - y) = x^2 - y^2
830 MultiPolynomial diff = (x + y) * (x - y);
831 EXPECT_EQ(diff.num_terms(), 2u);
832 EXPECT_DOUBLE_EQ(diff.coeff_at(Idx{2, 0, 0}), 1.0);
833 EXPECT_DOUBLE_EQ(diff.coeff_at(Idx{0, 2, 0}), -1.0);
834}
835
837{
838 auto x = MultiPolynomial::variable(2, 0);
839 auto y = MultiPolynomial::variable(2, 1);
840 MultiPolynomial one(2, 1.0);
841
842 MultiPolynomial a = x * x + y;
843 MultiPolynomial b = x + one;
844 MultiPolynomial c = y * y;
845
846 EXPECT_EQ((a + b) + c, a + (b + c));
847}
848
850{
851 // Verify (a*b)(pt) == a(pt) * b(pt)
852 auto x = MultiPolynomial::variable(2, 0);
853 auto y = MultiPolynomial::variable(2, 1);
854
855 MultiPolynomial a = x * x + y + MultiPolynomial(2, 1.0);
856 MultiPolynomial b = x * y + MultiPolynomial(2, -1.0);
857
858 Array<double> pt{2.0, 3.0};
859 EXPECT_DOUBLE_EQ((a * b).eval(pt), a.eval(pt) * b.eval(pt));
860}
861
862// ===================================================================
863// Edge cases
864// ===================================================================
865
867{
868 // f(x) = x^3 - 2x + 1
869 MultiPolynomial f(1, {
870 {Idx{3}, 1.0},
871 {Idx{1}, -2.0},
872 {Idx{0}, 1.0},
873 });
874
875 EXPECT_EQ(f.num_vars(), 1u);
876 EXPECT_EQ(f.degree(), 3u);
877
878 Array<double> pt{2.0};
879 // 8 - 4 + 1 = 5
880 EXPECT_DOUBLE_EQ(f.eval(pt), 5.0);
881}
882
884{
885 MultiPolynomial p(0, 42.0);
886 EXPECT_EQ(p.num_vars(), 0u);
888 EXPECT_DOUBLE_EQ(p.coeff_at(Idx{}), 42.0);
889
890 Array<double> pt; // empty point
891 EXPECT_DOUBLE_EQ(p.eval(pt), 42.0);
892}
893
895{
896 // x^10 * y^10
897 MultiPolynomial f(2, Idx{10, 10}, 1.0);
898 EXPECT_EQ(f.degree(), 20u);
899
900 Array<double> pt{2.0, 3.0};
901 // 2^10 * 3^10 = 1024 * 59049 = 60466176
902 EXPECT_DOUBLE_EQ(f.eval(pt), 60466176.0);
903}
904
906{
907 MultiPolynomial p(3, {
908 {Idx{1000, 0, 0}, 1.0},
909 {Idx{0, 750, 1}, 2.0},
910 {Idx{0, 0, 0}, 3.0},
911 });
912
913 EXPECT_EQ(p.degree(), 1000u);
914 EXPECT_EQ(p.num_terms(), 3u);
915
916 // Functional correctness checks (timing moved to benchmark suite)
917 Array<double> pt{1.01, 0.99, 1.02};
918 const double value = p.eval(pt);
919 EXPECT_GT(value, 0.0);
920
921 MultiPolynomial squared = p * p;
922 EXPECT_EQ(squared.degree(), 2000u);
923 EXPECT_LE(squared.num_terms(), 6u);
924
926 auto [q, r] = squared.divmod(divisors);
927 EXPECT_EQ(q(0), p);
928 EXPECT_TRUE(r.is_zero());
929}
930
931// ===================================================================
932// Layer 2: Industrial Fitting (Weighted Least-Squares)
933// ===================================================================
934
936{
937 // Fit f(x,y) = 2x + 3y using exact data
938 Array<std::pair<Array<double>, double>> data(3,
939 {Array<double>(), 0.0});
940 data(0) = {Array<double>{1.0, 1.0}, 5.0};
941 data(1) = {Array<double>{2.0, 1.0}, 7.0};
942 data(2) = {Array<double>{1.0, 2.0}, 8.0};
943
945 basis(0) = Idx{0, 0};
946 basis(1) = Idx{1, 0};
947 basis(2) = Idx{0, 1};
948
950
951 Array<double> pt_a{1.0, 1.0};
952 Array<double> pt_b{2.0, 1.0};
953 Array<double> pt_c{1.0, 2.0};
954
955 EXPECT_NEAR(p.eval(pt_a), 5.0, 1e-10);
956 EXPECT_NEAR(p.eval(pt_b), 7.0, 1e-10);
957 EXPECT_NEAR(p.eval(pt_c), 8.0, 1e-10);
958}
959
961{
962 // Same fit as above but using fit_weighted with uniform weights
963 Array<std::pair<Array<double>, double>> data(3,
964 {Array<double>(), 0.0});
965 data(0) = {Array<double>{1.0, 1.0}, 5.0};
966 data(1) = {Array<double>{2.0, 1.0}, 7.0};
967 data(2) = {Array<double>{1.0, 2.0}, 8.0};
968
970 basis(0) = Idx{0, 0};
971 basis(1) = Idx{1, 0};
972 basis(2) = Idx{0, 1};
973
974 Array<double> weights{1.0, 1.0, 1.0}; // Uniform weighting
975
977
978 Array<double> pt_a{1.0, 1.0};
979 EXPECT_NEAR(p.eval(pt_a), 5.0, 1e-10);
980}
981
983{
984 Array<std::pair<Array<double>, double>> data;
986 basis(0) = Idx{0};
987
989 std::domain_error);
990}
991
992// ===================================================================
993// Phase 1: Production-Grade Features
994// ===================================================================
995
997{
998 // Fit f(x,y) = 2x + 3y using ridge regression
999 Array<std::pair<Array<double>, double>> data(3,
1000 {Array<double>(), 0.0});
1001 data(0) = {Array<double>{1.0, 1.0}, 5.0};
1002 data(1) = {Array<double>{2.0, 1.0}, 7.0};
1003 data(2) = {Array<double>{1.0, 2.0}, 8.0};
1004
1006 basis(0) = Idx{0, 0}; // constant
1007 basis(1) = Idx{1, 0}; // x
1008 basis(2) = Idx{0, 1}; // y
1009
1010 double lambda_used = 0.0;
1011 double gcv_score = 0.0;
1013 data, 2, basis, &lambda_used, &gcv_score);
1014
1015 // Ridge should recover function reasonably well (within regularization tolerance)
1016 EXPECT_NEAR(p.eval(Array<double>{1.0, 1.0}), 5.0, 1e-4);
1017 EXPECT_NEAR(p.eval(Array<double>{2.0, 1.0}), 7.0, 1e-4);
1018
1019 // Lambda should be non-negative
1020 EXPECT_GE(lambda_used, 0.0);
1021 // GCV score should be positive
1022 EXPECT_GT(gcv_score, 0.0);
1023}
1024
1026{
1027 // Ridge should be similar to plain fit on exact data
1028 Array<std::pair<Array<double>, double>> data(4,
1029 {Array<double>(), 0.0});
1030 data(0) = {Array<double>{0.0, 0.0}, 1.0};
1031 data(1) = {Array<double>{1.0, 0.0}, 3.0};
1032 data(2) = {Array<double>{0.0, 1.0}, 4.0};
1033 data(3) = {Array<double>{1.0, 1.0}, 6.0};
1034
1036 basis(0) = Idx{0, 0};
1037 basis(1) = Idx{1, 0};
1038 basis(2) = Idx{0, 1};
1039
1042
1043 // Both should fit well (ridge may be slightly off due to regularization)
1044 EXPECT_NEAR(p_plain.eval(Array<double>{1.0, 1.0}), 6.0, 1e-10);
1045 EXPECT_NEAR(p_ridge.eval(Array<double>{1.0, 1.0}), 6.0, 1e-4);
1046}
1047
1049{
1050 // Ridge handles noisy quadratic data
1051 Array<std::pair<Array<double>, double>> data(5,
1052 {Array<double>(), 0.0});
1053 // y = x^2, with noise
1054 data(0) = {Array<double>{0.0}, 0.1};
1055 data(1) = {Array<double>{1.0}, 1.05};
1056 data(2) = {Array<double>{2.0}, 4.15};
1057 data(3) = {Array<double>{3.0}, 9.0};
1058 data(4) = {Array<double>{4.0}, 16.1};
1059
1061 basis(0) = Idx{0}; // constant
1062 basis(1) = Idx{1}; // x
1063 basis(2) = Idx{2}; // x^2
1064
1065 double lambda_used = 0.0;
1067
1068 // Ridge should handle noise gracefully
1069 EXPECT_NEAR(p.eval(Array<double>{2.0}), 4.0, 0.5);
1070
1071 // Should choose a reasonable lambda
1072 EXPECT_GE(lambda_used, 0.0);
1073}
1074
1076{
1077 // Exact fit should have R² ≈ 1 and RMSE ≈ 0
1078 Array<std::pair<Array<double>, double>> data(3,
1079 {Array<double>(), 0.0});
1080 data(0) = {Array<double>{1.0, 1.0}, 5.0};
1081 data(1) = {Array<double>{2.0, 1.0}, 7.0};
1082 data(2) = {Array<double>{1.0, 2.0}, 8.0};
1083
1085 basis(0) = Idx{0, 0};
1086 basis(1) = Idx{1, 0};
1087 basis(2) = Idx{0, 1};
1088
1090 PolyFitAnalysis<double> stats = analyze_fit(p, data);
1091
1092 // Perfect fit
1093 EXPECT_NEAR(stats.r_squared, 1.0, 1e-10);
1094 EXPECT_NEAR(stats.rmse, 0.0, 1e-10);
1095 EXPECT_EQ(stats.residuals.size(), 3u);
1096 EXPECT_NEAR(stats.residuals(0), 0.0, 1e-10);
1097}
1098
1100{
1101 // Fit line to noisy data
1102 Array<std::pair<Array<double>, double>> data(4,
1103 {Array<double>(), 0.0});
1104 // y = 2x, but with noise
1105 data(0) = {Array<double>{1.0}, 2.0};
1106 data(1) = {Array<double>{2.0}, 4.1}; // +0.1 error
1107 data(2) = {Array<double>{3.0}, 6.0};
1108 data(3) = {Array<double>{4.0}, 7.9}; // -0.1 error
1109
1111 basis(0) = Idx{0}; // constant
1112 basis(1) = Idx{1}; // x
1113
1115 PolyFitAnalysis<double> stats = analyze_fit(p, data);
1116
1117 // Should have high R² (not perfect due to noise)
1118 EXPECT_GT(stats.r_squared, 0.95);
1119 EXPECT_LT(stats.r_squared, 1.0);
1120
1121 // RMSE should reflect noise level
1122 EXPECT_GT(stats.rmse, 0.0);
1123 EXPECT_LT(stats.rmse, 0.2);
1124
1125 // TSS = ESS + RSS
1126 EXPECT_NEAR(stats.tss, stats.ess + stats.rss, 1e-10);
1127
1128 // Residuals vector should have correct size
1129 EXPECT_EQ(stats.residuals.size(), 4u);
1130}
1131
1133{
1134 // Validate mean_y and sum of squares calculations
1135 Array<std::pair<Array<double>, double>> data(3,
1136 {Array<double>(), 0.0});
1137 data(0) = {Array<double>{0.0}, 1.0};
1138 data(1) = {Array<double>{1.0}, 2.0};
1139 data(2) = {Array<double>{2.0}, 3.0}; // mean = 2.0
1140
1141 // Fit y = x + 1 (linear model)
1143 basis(0) = Idx{0}; // constant
1144 basis(1) = Idx{1}; // x
1145
1147 PolyFitAnalysis<double> stats = analyze_fit(p, data);
1148
1149 // Mean should be 2.0
1150 EXPECT_NEAR(stats.mean_y, 2.0, 1e-10);
1151
1152 // TSS = sum((y_i - mean)^2) = 1 + 0 + 1 = 2
1153 EXPECT_NEAR(stats.tss, 2.0, 1e-10);
1154
1155 // Fitting linear model to linear data: exact fit
1156 EXPECT_NEAR(stats.rmse, 0.0, 1e-10);
1157 EXPECT_NEAR(stats.r_squared, 1.0, 1e-10);
1158
1159 // Verify TSS = ESS + RSS
1160 EXPECT_NEAR(stats.tss, stats.ess + stats.rss, 1e-10);
1161}
1162
1164{
1165 Array<std::pair<Array<long long>, long long>> data(2, {Array<long long>(), 0LL});
1166 data(0) = {Array<long long>{0LL}, 0LL};
1167 data(1) = {Array<long long>{1LL}, 1LL};
1168
1170 PolyFitAnalysis<long long> stats = analyze_fit(p, data);
1171
1172 EXPECT_DOUBLE_EQ(stats.mean_y, 0.5);
1173 EXPECT_DOUBLE_EQ(stats.tss, 0.5);
1174 EXPECT_EQ(stats.residuals(0), 0LL);
1175 EXPECT_EQ(stats.residuals(1), 1LL);
1176}
1177
1179{
1180 // Test combined workflow: ridge fit + residual analysis
1181 Array<std::pair<Array<double>, double>> data(6,
1182 {Array<double>(), 0.0});
1183 // Noisy cubic
1184 for (size_t i = 0; i < 6; ++i)
1185 {
1186 double x = i - 2.5;
1187 data(i) = {Array<double>{x}, x * x * x + 0.5 * std::sin(i)};
1188 }
1189
1191 basis(0) = Idx{0};
1192 basis(1) = Idx{1};
1193 basis(2) = Idx{2};
1194 basis(3) = Idx{3};
1195
1196 double lambda_used = 0.0;
1198 PolyFitAnalysis<double> stats = analyze_fit(p, data);
1199
1200 // Ridge should choose a lambda
1201 EXPECT_GE(lambda_used, 0.0);
1202
1203 // Should fit reasonably well (not perfect due to noise)
1204 EXPECT_GT(stats.r_squared, 0.8);
1205 EXPECT_LT(stats.rmse, 1.0);
1206
1207 // Verify calculation consistency
1208 EXPECT_NEAR(stats.tss, stats.ess + stats.rss, 1e-8);
1209 EXPECT_NEAR(stats.r_squared, stats.ess / stats.tss, 1e-10);
1210}
1211
1212// ===================================================================
1213// Phase 2: Differential Calculus, Interpolation, Serialization, Parallelism
1214// ===================================================================
1215
1216class MultiPolyPhase2 : public ::testing::Test
1217{
1218};
1219
1220// ===================================================================
1221// Phase 2 — Differential Calculus: Partial Derivatives
1222// ===================================================================
1223
1225{
1226 MultiPolynomial p(2, 5.0); // constant
1227 MultiPolynomial dp = p.partial(0);
1228 EXPECT_TRUE(dp.is_zero());
1229}
1230
1232{
1233 // f(x,y) = 3x + 2y → ∂f/∂x = 3
1234 MultiPolynomial f(2, {
1235 {Idx{1, 0}, 3.0},
1236 {Idx{0, 1}, 2.0},
1237 });
1238 MultiPolynomial df = f.partial(0);
1239 EXPECT_EQ(df.num_terms(), 1u);
1240 EXPECT_DOUBLE_EQ(df.coeff_at(Idx{0, 0}), 3.0);
1241}
1242
1244{
1245 // f(x,y) = 3x + 2y → ∂f/∂y = 2
1246 MultiPolynomial f(2, {
1247 {Idx{1, 0}, 3.0},
1248 {Idx{0, 1}, 2.0},
1249 });
1250 MultiPolynomial df = f.partial(1);
1251 EXPECT_EQ(df.num_terms(), 1u);
1252 EXPECT_DOUBLE_EQ(df.coeff_at(Idx{0, 0}), 2.0);
1253}
1254
1256{
1257 // f(x,y) = x^2 + 3xy + y^2 → ∂f/∂x = 2x + 3y
1258 MultiPolynomial f(2, {
1259 {Idx{2, 0}, 1.0},
1260 {Idx{1, 1}, 3.0},
1261 {Idx{0, 2}, 1.0},
1262 });
1263 MultiPolynomial df = f.partial(0);
1264 EXPECT_EQ(df.num_terms(), 2u);
1265 EXPECT_DOUBLE_EQ(df.coeff_at(Idx{1, 0}), 2.0);
1266 EXPECT_DOUBLE_EQ(df.coeff_at(Idx{0, 1}), 3.0);
1267}
1268
1270{
1271 // f(x,y) = x^2 → ∂²f/∂y² = 0 (x^2 doesn't depend on y)
1272 auto x = MultiPolynomial::variable(2, 0);
1273 MultiPolynomial f = x * x;
1274 MultiPolynomial ddf = f.partial(1, 2);
1275 EXPECT_TRUE(ddf.is_zero());
1276}
1277
1279{
1280 // f(x) = x^3 → ∂f/∂x = 3x^2, ∂²f/∂x² = 6x, ∂³f/∂x³ = 6
1281 MultiPolynomial f(1, Idx{3}, 1.0);
1282
1283 MultiPolynomial df1 = f.partial(0, 1);
1284 EXPECT_DOUBLE_EQ(df1.coeff_at(Idx{2}), 3.0);
1285
1286 MultiPolynomial df2 = f.partial(0, 2);
1287 EXPECT_DOUBLE_EQ(df2.coeff_at(Idx{1}), 6.0);
1288
1289 MultiPolynomial df3 = f.partial(0, 3);
1290 EXPECT_DOUBLE_EQ(df3.coeff_at(Idx{0}), 6.0);
1291}
1292
1294{
1295 // ∂⁰f/∂x⁰ = f
1296 auto x = MultiPolynomial::variable(2, 0);
1297 auto y = MultiPolynomial::variable(2, 1);
1298 MultiPolynomial f = x + y;
1299 EXPECT_EQ(f.partial(0, 0), f);
1300}
1301
1303{
1304 MultiPolynomial f(2, 1.0);
1305 EXPECT_THROW(f.partial(2), std::domain_error); // var 2 >= nvars 2
1306}
1307
1308// ===================================================================
1309// Phase 2 — Gradient and Hessian
1310// ===================================================================
1311
1313{
1314 MultiPolynomial f(3, 1.0);
1315 auto grad = f.gradient();
1316 EXPECT_EQ(grad.size(), 3u);
1317}
1318
1320{
1321 // f(x,y) = 2x + 3y → ∇f = [2, 3]
1322 MultiPolynomial f(2, {
1323 {Idx{1, 0}, 2.0},
1324 {Idx{0, 1}, 3.0},
1325 });
1326 auto grad = f.gradient();
1327 EXPECT_DOUBLE_EQ(grad(0).coeff_at(Idx{0, 0}), 2.0);
1328 EXPECT_DOUBLE_EQ(grad(1).coeff_at(Idx{0, 0}), 3.0);
1329}
1330
1332{
1333 // f(x,y) = x^2 + 2xy + y^2 → ∇f = [2x + 2y, 2x + 2y]
1334 MultiPolynomial f(2, {
1335 {Idx{2, 0}, 1.0},
1336 {Idx{1, 1}, 2.0},
1337 {Idx{0, 2}, 1.0},
1338 });
1339 auto grad = f.gradient();
1340 EXPECT_EQ(grad.size(), 2u);
1341 EXPECT_EQ(grad(0).num_terms(), 2u);
1342 EXPECT_EQ(grad(1).num_terms(), 2u);
1343}
1344
1346{
1347 MultiPolynomial f(2, 1.0);
1348 auto hess = f.hessian();
1349 EXPECT_EQ(hess.size(), 2u);
1350 EXPECT_EQ(hess(0).size(), 2u);
1351 EXPECT_EQ(hess(1).size(), 2u);
1352}
1353
1355{
1356 // Linear polynomial has zero Hessian
1357 auto x = MultiPolynomial::variable(2, 0);
1358 auto y = MultiPolynomial::variable(2, 1);
1359 MultiPolynomial f = x + y;
1360 auto H = f.hessian();
1361 for (size_t i = 0; i < 2; ++i)
1362 for (size_t j = 0; j < 2; ++j)
1363 EXPECT_TRUE(H(i)(j).is_zero());
1364}
1365
1367{
1368 // f(x,y) = x^2 + 4xy + 3y^2
1369 // H = [[2, 4], [4, 6]]
1370 MultiPolynomial f(2, {
1371 {Idx{2, 0}, 1.0},
1372 {Idx{1, 1}, 4.0},
1373 {Idx{0, 2}, 3.0},
1374 });
1375 auto H = f.hessian();
1376 EXPECT_DOUBLE_EQ(H(0)(0).coeff_at(Idx{0, 0}), 2.0);
1377 EXPECT_DOUBLE_EQ(H(0)(1).coeff_at(Idx{0, 0}), 4.0);
1378 EXPECT_DOUBLE_EQ(H(1)(0).coeff_at(Idx{0, 0}), 4.0);
1379 EXPECT_DOUBLE_EQ(H(1)(1).coeff_at(Idx{0, 0}), 6.0);
1380}
1381
1383{
1384 // H(i,j) == H(j,i)
1385 auto x = MultiPolynomial::variable(2, 0);
1386 auto y = MultiPolynomial::variable(2, 1);
1387 MultiPolynomial f = x * x + x * y + y * y;
1388 auto H = f.hessian();
1389 EXPECT_EQ(H(0)(1), H(1)(0));
1390}
1391
1392// ===================================================================
1393// Phase 2 — Evaluating Gradient and Hessian at Points
1394// ===================================================================
1395
1397{
1398 // f(x,y) = 2x + 3y → ∇f(x,y) = [2, 3] at any point
1399 MultiPolynomial f(2, {
1400 {Idx{1, 0}, 2.0},
1401 {Idx{0, 1}, 3.0},
1402 });
1403 auto grad = f.eval_gradient(Array<double>{5.0, 7.0});
1404 EXPECT_EQ(grad.size(), 2u);
1405 EXPECT_DOUBLE_EQ(grad(0), 2.0);
1406 EXPECT_DOUBLE_EQ(grad(1), 3.0);
1407}
1408
1410{
1411 // f(x,y) = x^2 + y^2 → ∇f(3,4) = [6, 8]
1412 MultiPolynomial f(2, {
1413 {Idx{2, 0}, 1.0},
1414 {Idx{0, 2}, 1.0},
1415 });
1416 auto grad = f.eval_gradient(Array<double>{3.0, 4.0});
1417 EXPECT_DOUBLE_EQ(grad(0), 6.0);
1418 EXPECT_DOUBLE_EQ(grad(1), 8.0);
1419}
1420
1422{
1423 // f(x,y) = x^2 + 4xy + 3y^2
1424 // H = [[2, 4], [4, 6]] (constant)
1425 MultiPolynomial f(2, {
1426 {Idx{2, 0}, 1.0},
1427 {Idx{1, 1}, 4.0},
1428 {Idx{0, 2}, 3.0},
1429 });
1430 auto H = f.eval_hessian(Array<double>{1.0, 1.0});
1431 EXPECT_DOUBLE_EQ(H(0)(0), 2.0);
1432 EXPECT_DOUBLE_EQ(H(0)(1), 4.0);
1433 EXPECT_DOUBLE_EQ(H(1)(0), 4.0);
1434 EXPECT_DOUBLE_EQ(H(1)(1), 6.0);
1435}
1436
1438{
1439 MultiPolynomial f(3, 1.0);
1440 EXPECT_THROW(f.eval_gradient(Array<double>{1.0, 2.0}), std::domain_error);
1441}
1442
1444{
1445 MultiPolynomial f(3, 1.0);
1446 EXPECT_THROW(f.eval_hessian(Array<double>{1.0, 2.0}), std::domain_error);
1447}
1448
1449// ===================================================================
1450// Phase 2 — Interpolation
1451// ===================================================================
1452
1454{
1455 // f(x) passing through (0, 1), (1, 2)
1457 grid(0) = Array<double>(2, 0.0);
1458 grid(0)(0) = 0.0;
1459 grid(0)(1) = 1.0;
1460
1461 Array<double> values(2, 0.0);
1462 values(0) = 1.0;
1463 values(1) = 2.0;
1464
1466
1467 Array<double> pt0(1, 0.0);
1468 Array<double> pt1(1, 1.0);
1469 Array<double> pt_half(1, 0.5);
1470
1471 EXPECT_NEAR(p.eval(pt0), 1.0, 1e-10);
1472 EXPECT_NEAR(p.eval(pt1), 2.0, 1e-10);
1473 EXPECT_NEAR(p.eval(pt_half), 1.5, 1e-10);
1474}
1475
1477{
1478 // f(x) = x^2 on grid [0, 1, 2]
1480 grid(0) = Array<double>(3, 0.0);
1481 grid(0)(0) = 0.0;
1482 grid(0)(1) = 1.0;
1483 grid(0)(2) = 2.0;
1484
1485 Array<double> values(3, 0.0);
1486 values(0) = 0.0;
1487 values(1) = 1.0;
1488 values(2) = 4.0;
1489
1491
1492 Array<double> pt0(1, 0.0);
1493 Array<double> pt1(1, 1.0);
1494 Array<double> pt2(1, 2.0);
1495
1496 EXPECT_NEAR(p.eval(pt0), 0.0, 1e-10);
1497 EXPECT_NEAR(p.eval(pt1), 1.0, 1e-10);
1498 EXPECT_NEAR(p.eval(pt2), 4.0, 1e-10);
1499}
1500
1502{
1503 // f(x,y) = x + y on 2x2 grid: {(0,1)→1, (0,2)→2, (1,1)→2, (1,2)→3}
1505 grid(0) = Array<double>(2, 0.0);
1506 grid(0)(0) = 0.0;
1507 grid(0)(1) = 1.0;
1508 grid(1) = Array<double>(2, 0.0);
1509 grid(1)(0) = 1.0;
1510 grid(1)(1) = 2.0;
1511
1512 Array<double> values(4, 0.0);
1513 values(0) = 1.0;
1514 values(1) = 2.0;
1515 values(2) = 2.0;
1516 values(3) = 3.0;
1517
1519
1520 Array<double> pt00(2, 0.0);
1521 pt00(1) = 1.0;
1522 Array<double> pt01(2, 0.0);
1523 pt01(1) = 2.0;
1524 Array<double> pt10(2, 0.0);
1525 pt10(0) = 1.0;
1526 pt10(1) = 1.0;
1527 Array<double> pt11(2, 0.0);
1528 pt11(0) = 1.0;
1529 pt11(1) = 2.0;
1530
1531 EXPECT_NEAR(p.eval(pt00), 1.0, 1e-10);
1532 EXPECT_NEAR(p.eval(pt01), 2.0, 1e-10);
1533 EXPECT_NEAR(p.eval(pt10), 2.0, 1e-10);
1534 EXPECT_NEAR(p.eval(pt11), 3.0, 1e-10);
1535}
1536
1538{
1539 // 3x2 grid with simple function values
1541 grid(0) = Array<double>(3, 0.0);
1542 grid(0)(0) = 0.0;
1543 grid(0)(1) = 1.0;
1544 grid(0)(2) = 2.0;
1545 grid(1) = Array<double>(2, 0.0);
1546 grid(1)(0) = 0.0;
1547 grid(1)(1) = 1.0;
1548
1549 Array<double> values(6, 0.0);
1550 // values[i*2 + j] for x=grid(0)(i), y=grid(1)(j)
1551 // f(0,0)=1, f(0,1)=2, f(1,0)=2, f(1,1)=3, f(2,0)=3, f(2,1)=4
1552 values(0) = 1.0;
1553 values(1) = 2.0;
1554 values(2) = 2.0;
1555 values(3) = 3.0;
1556 values(4) = 3.0;
1557 values(5) = 4.0;
1558
1560
1561 Array<double> pt00(2, 0.0);
1562 Array<double> pt01(2, 0.0);
1563 pt01(1) = 1.0;
1564 Array<double> pt10(2, 0.0);
1565 pt10(0) = 1.0;
1566 Array<double> pt11(2, 0.0);
1567 pt11(0) = 1.0;
1568 pt11(1) = 1.0;
1569 Array<double> pt20(2, 0.0);
1570 pt20(0) = 2.0;
1571 Array<double> pt21(2, 0.0);
1572 pt21(0) = 2.0;
1573 pt21(1) = 1.0;
1574
1575 EXPECT_NEAR(p.eval(pt00), 1.0, 1e-10);
1576 EXPECT_NEAR(p.eval(pt01), 2.0, 1e-10);
1577 EXPECT_NEAR(p.eval(pt10), 2.0, 1e-10);
1578 EXPECT_NEAR(p.eval(pt11), 3.0, 1e-10);
1579 EXPECT_NEAR(p.eval(pt20), 3.0, 1e-10);
1580 EXPECT_NEAR(p.eval(pt21), 4.0, 1e-10);
1581}
1582
1584{
1586 grid(0) = Array<double>{0.0, 1.0};
1587 grid(1) = Array<double>{0.0, 1.0};
1588
1589 Array<double> values{1.0, 2.0}; // Wrong size (need 4)
1590
1591 EXPECT_THROW(MultiPolynomial::interpolate(grid, values, 2), std::domain_error);
1592}
1593
1595{
1597 grid(0) = Array<double>{0.0, 1.0, 1.0}; // Duplicate nodes!
1598
1599 Array<double> values{1.0, 2.0, 3.0};
1600
1601 EXPECT_THROW(MultiPolynomial::interpolate(grid, values, 1), std::domain_error);
1602}
1603
1604// ===================================================================
1605// Phase 2 — Serialization: JSON
1606// ===================================================================
1607
1609{
1610 // f(x,y) = 2x + 3xy
1611 MultiPolynomial f(2, {
1612 {Idx{1, 0}, 2.0},
1613 {Idx{1, 1}, 3.0},
1614 });
1615
1616 std::string json = f.to_json();
1618
1619 EXPECT_EQ(f, g);
1620}
1621
1623{
1625 std::string json = z.to_json();
1627 EXPECT_TRUE(z2.is_zero());
1628}
1629
1630// ===================================================================
1631// Phase 2 — Serialization: Binary
1632// ===================================================================
1633
1635{
1636 MultiPolynomial f(2, {
1637 {Idx{2, 0}, 5.5},
1638 {Idx{0, 1}, -2.3},
1639 });
1640
1641 std::ostringstream oss;
1642 f.to_binary(oss);
1643
1644 std::istringstream iss(oss.str());
1646
1647 EXPECT_EQ(f, g);
1648}
1649
1651{
1652 MultiPolynomial f(3, {
1653 {Idx{1, 0, 0}, 1.0},
1654 {Idx{0, 1, 0}, 2.0},
1655 {Idx{0, 0, 1}, 3.0},
1656 });
1657
1658 std::ostringstream oss;
1659 f.to_binary(oss);
1660
1661 std::istringstream iss(oss.str());
1663
1664 EXPECT_EQ(f.num_vars(), g.num_vars());
1665 EXPECT_EQ(f.num_terms(), g.num_terms());
1666 EXPECT_DOUBLE_EQ(g.coeff_at(Idx{1, 0, 0}), 1.0);
1667 EXPECT_DOUBLE_EQ(g.coeff_at(Idx{0, 1, 0}), 2.0);
1668 EXPECT_DOUBLE_EQ(g.coeff_at(Idx{0, 0, 1}), 3.0);
1669}
1670
1671// ===================================================================
1672// Phase 2 — Parallelism
1673// ===================================================================
1674
1676{
1677 // f(x) = x^2
1678 auto x = MultiPolynomial::variable(1, 0);
1679 MultiPolynomial f = x * x;
1680
1682 pts(0) = Array<double>{2.0};
1683 pts(1) = Array<double>{3.0};
1684 pts(2) = Array<double>{4.0};
1685
1686 auto results = f.eval_batch(pts);
1687
1688 EXPECT_EQ(results.size(), 3u);
1689 EXPECT_DOUBLE_EQ(results(0), 4.0); // 2^2
1690 EXPECT_DOUBLE_EQ(results(1), 9.0); // 3^2
1691 EXPECT_DOUBLE_EQ(results(2), 16.0); // 4^2
1692}
1693
1695{
1696 // f(x,y) = x + 2y
1697 MultiPolynomial f(2, {
1698 {Idx{1, 0}, 1.0},
1699 {Idx{0, 1}, 2.0},
1700 });
1701
1703 pts(0) = Array<double>{1.0, 2.0};
1704 pts(1) = Array<double>{3.0, 4.0};
1705
1706 auto batch_results = f.eval_batch(pts);
1707
1708 EXPECT_NEAR(batch_results(0), f.eval(pts(0)), 1e-14);
1709 EXPECT_NEAR(batch_results(1), f.eval(pts(1)), 1e-14);
1710}
1711
1713{
1714 // Create simple data: y = 2x + 1
1715 Array<std::pair<Array<double>, double>> data(4, {Array<double>(), 0.0});
1716 data(0) = {Array<double>{0.0}, 1.0};
1717 data(1) = {Array<double>{1.0}, 3.0};
1718 data(2) = {Array<double>{2.0}, 5.0};
1719 data(3) = {Array<double>{3.0}, 7.0};
1720
1722 basis(0) = Idx{0}; // constant
1723 basis(1) = Idx{1}; // x
1724
1725 // Fit with sequential method
1727
1728 // Fit with parallel method
1730
1731 // Results should be virtually identical (within floating-point tolerance)
1732 EXPECT_NEAR(p_seq.eval(Array<double>{0.5}), p_par.eval(Array<double>{0.5}), 1e-10);
1733 EXPECT_NEAR(p_seq.eval(Array<double>{1.5}), p_par.eval(Array<double>{1.5}), 1e-10);
1734}
1735
1736// ===================================================================
1737// Layer 3 — Gröbner Bases and Ideal Operations
1738// ===================================================================
1739
1740// ---
1741// divmod tests (8)
1742// ---
1743
1745{
1746 // f = 2x + 3, divisor = 2
1747 MultiPolynomial f(1, {{Idx{1}, 1.0}, {Idx{0}, 3.0}});
1749 divisors(0) = MultiPolynomial(1, 2.0);
1750
1751 auto [q, r] = f.divmod(divisors);
1752 EXPECT_EQ(q.size(), 1u);
1753 EXPECT_EQ(q(0).num_terms(), 2u);
1754 EXPECT_DOUBLE_EQ(q(0).coeff_at(Idx{1}), 0.5);
1755 EXPECT_DOUBLE_EQ(q(0).coeff_at(Idx{0}), 1.5);
1756 EXPECT_TRUE(r.is_zero());
1757}
1758
1760{
1761 // f = x^2, divisor = x
1762 auto x = MultiPolynomial::variable(1, 0);
1763 MultiPolynomial f = x * x;
1765
1766 auto [q, r] = f.divmod(divisors);
1767 EXPECT_EQ(q(0).num_terms(), 1u);
1768 EXPECT_DOUBLE_EQ(q(0).coeff_at(Idx{1}), 1.0);
1769 EXPECT_TRUE(r.is_zero());
1770}
1771
1773{
1774 // f = x^2 + 1, divisor = x
1775 auto x = MultiPolynomial::variable(1, 0);
1776 MultiPolynomial f = x * x + MultiPolynomial(1, 1.0);
1778
1779 auto [q, r] = f.divmod(divisors);
1780 EXPECT_EQ(q(0).num_terms(), 1u);
1781 EXPECT_DOUBLE_EQ(q(0).coeff_at(Idx{1}), 1.0);
1782 EXPECT_EQ(r.num_terms(), 1u);
1783 EXPECT_DOUBLE_EQ(r.coeff_at(Idx{0}), 1.0);
1784}
1785
1787{
1788 // f = xy + x + y + 1 = (x + 1)(y + 1)
1789 // divisors = [x + 1, y + 1]
1790 auto x = MultiPolynomial::variable(2, 0);
1791 auto y = MultiPolynomial::variable(2, 1);
1792 MultiPolynomial f = x * y + x + y + MultiPolynomial(2, 1.0);
1793
1795 divisors(0) = x + MultiPolynomial(2, 1.0);
1796 divisors(1) = y + MultiPolynomial(2, 1.0);
1797
1798 auto [q, r] = f.divmod(divisors);
1799 EXPECT_EQ(q.size(), 2u);
1800 EXPECT_TRUE(r.is_zero() or r.num_terms() == 0u);
1801}
1802
1804{
1805 // f = 0, divisor = x
1806 MultiPolynomial f(1, 0.0); // Zero polynomial with 1 variable
1807 auto x = MultiPolynomial::variable(1, 0);
1809
1810 auto [q, r] = f.divmod(divisors);
1811 EXPECT_EQ(q.size(), 1u);
1812 EXPECT_TRUE(q(0).is_zero());
1813 EXPECT_TRUE(r.is_zero());
1814}
1815
1817{
1818 // divisors contain a zero polynomial
1819 MultiPolynomial f(1, 1.0);
1821
1822 EXPECT_THROW(f.divmod(divisors), std::domain_error);
1823}
1824
1826{
1827 // empty divisor array
1828 MultiPolynomial f(1, 1.0);
1830
1831 EXPECT_THROW(f.divmod(divisors), std::domain_error);
1832}
1833
1835{
1836 // Test: f = (q * g) + r
1837 auto x = MultiPolynomial::variable(1, 0);
1838 MultiPolynomial f = x * x * x - MultiPolynomial(1, 1.0); // x^3 - 1
1839 MultiPolynomial g = x - MultiPolynomial(1, 1.0); // x - 1
1840
1842 auto [q, r] = f.divmod(divisors);
1843
1844 MultiPolynomial reconstructed = q(0) * g + r;
1845 // Check they have the same terms
1846 EXPECT_EQ(reconstructed.num_terms(), f.num_terms());
1847}
1848
1861
1862// ---
1863// s_poly tests (5)
1864// ---
1865
1867{
1868 // f = x, g = y (monomials)
1869 auto f = MultiPolynomial::variable(2, 0);
1870 auto g = MultiPolynomial::variable(2, 1);
1871
1873
1874 // lcm(x, y) = xy, so S(x, y) = (xy/x) * x - (xy/y) * y = y * x - x * y = 0
1875 EXPECT_TRUE(s.is_zero());
1876}
1877
1879{
1880 // f = 2x, g = 3x (both same monomial)
1881 MultiPolynomial f(1, {{Idx{1}, 2.0}});
1882 MultiPolynomial g(1, {{Idx{1}, 3.0}});
1883
1885
1886 // lcm(x, x) = x, S(f, g) = (x/x) * (1/2) * 2x - (x/x) * (1/3) * 3x = x - x = 0
1887 EXPECT_TRUE(s.is_zero());
1888}
1889
1891{
1892 MultiPolynomial zero;
1893 auto g = MultiPolynomial::variable(1, 0);
1894
1895 EXPECT_THROW(MultiPolynomial::s_poly(zero, g), std::domain_error);
1896}
1897
1899{
1900 auto f = MultiPolynomial::variable(1, 0);
1901 MultiPolynomial zero;
1902
1903 EXPECT_THROW(MultiPolynomial::s_poly(f, zero), std::domain_error);
1904}
1905
1907{
1908 auto f = MultiPolynomial::variable(1, 0);
1909 auto g = MultiPolynomial::variable(2, 0);
1910
1911 EXPECT_THROW(MultiPolynomial::s_poly(f, g), std::invalid_argument);
1912}
1913
1914// ---
1915// groebner_basis tests (5)
1916// ---
1917
1919{
1920 // Single generator: basis is itself
1921 auto x = MultiPolynomial::variable(1, 0);
1922 Array<MultiPolynomial> gens(1, x * x - MultiPolynomial(1, 1.0));
1923
1925
1926 EXPECT_GE(basis.size(), 1u);
1927 EXPECT_EQ(basis(0).num_vars(), 1u);
1928}
1929
1931{
1932 // Linear ideal: x + 1, y - 2
1933 auto x = MultiPolynomial::variable(2, 0);
1934 auto y = MultiPolynomial::variable(2, 1);
1935
1937 gens(0) = x + MultiPolynomial(2, 1.0);
1938 gens(1) = y - MultiPolynomial(2, 2.0);
1939
1941
1942 // Linear ideal should have a basis
1943 EXPECT_GE(basis.size(), 2u);
1944}
1945
1947{
1948 auto x = MultiPolynomial::variable(1, 0);
1949
1951 gens(0) = x;
1952 gens(1) = 2.0 * x;
1953 gens(2) = x;
1954
1956
1957 ASSERT_EQ(basis.size(), 1u);
1958 EXPECT_NEAR(basis(0).leading_coeff(), 1.0, 1e-12);
1959 EXPECT_TRUE((basis(0) - x).is_zero());
1960}
1961
1963{
1964 // Quadratic: x^2 - y, xy - 1
1965 auto x = MultiPolynomial::variable(2, 0);
1966 auto y = MultiPolynomial::variable(2, 1);
1967
1969 gens(0) = x * x - y;
1970 gens(1) = x * y - MultiPolynomial(2, 1.0);
1971
1973
1974 EXPECT_GE(basis.size(), 1u);
1975 EXPECT_EQ(basis(0).num_vars(), 2u);
1976}
1977
1979{
1981
1982 auto x = LexPoly::variable(2, 0);
1983 auto y = LexPoly::variable(2, 1);
1984
1986 gens(0) = x * x - y;
1987 gens(1) = x * y - LexPoly(2, 1.0);
1988
1989 Array<LexPoly> basis = LexPoly::groebner_basis(gens);
1990
1991 ASSERT_EQ(basis.size(), 2u);
1992
1993 bool found_x_minus_y2 = false;
1994 bool found_y3_minus_1 = false;
1995 for (size_t i = 0; i < basis.size(); ++i)
1996 {
1997 EXPECT_NEAR(basis(i).leading_coeff(), 1.0, 1e-12);
1998 if ((basis(i) - (x - y * y)).is_zero())
1999 found_x_minus_y2 = true;
2000 if ((basis(i) - (y * y * y - LexPoly(2, 1.0))).is_zero())
2001 found_y3_minus_1 = true;
2002 }
2003
2008}
2009
2011{
2013
2014 auto x = LexPoly::variable(3, 0);
2015 auto y = LexPoly::variable(3, 1);
2016 auto z = LexPoly::variable(3, 2);
2017
2019 gens(0) = x * y - LexPoly(3, 1.0);
2020 gens(1) = y - z;
2021 gens(2) = x - LexPoly(3, 1.0);
2022
2023 Array<LexPoly> basis = LexPoly::groebner_basis(gens);
2024
2025 EXPECT_TRUE((x - LexPoly(3, 1.0)).reduce_modulo(basis).is_zero());
2026 EXPECT_TRUE((y - LexPoly(3, 1.0)).reduce_modulo(basis).is_zero());
2027 EXPECT_TRUE((z - LexPoly(3, 1.0)).reduce_modulo(basis).is_zero());
2028}
2029
2031{
2033
2034 std::mt19937 rng(20260317u);
2035 std::uniform_int_distribution<int> coeff_dist(-2, 2);
2036 std::uniform_int_distribution<int> keep_dist(0, 1);
2037
2038 Array<Idx> monomials(6, Idx{});
2039 monomials(0) = Idx{2, 0};
2040 monomials(1) = Idx{1, 1};
2041 monomials(2) = Idx{0, 2};
2042 monomials(3) = Idx{1, 0};
2043 monomials(4) = Idx{0, 1};
2044 monomials(5) = Idx{0, 0};
2045
2046 for (size_t trial = 0; trial < 10; ++trial)
2047 {
2049
2050 for (size_t g = 0; g < gens.size(); ++g)
2051 {
2052 LexPoly p(2);
2053 do
2054 {
2055 p = LexPoly(2);
2056 for (size_t m = 0; m < monomials.size(); ++m)
2057 {
2058 if (keep_dist(rng) == 0)
2059 continue;
2060
2061 const int coeff = coeff_dist(rng);
2062 if (coeff == 0)
2063 continue;
2064
2065 p.add_to_coeff(monomials(m), static_cast<double>(coeff));
2066 }
2067 }
2068 while (p.is_zero() or p.degree() == 0);
2069
2070 gens(g) = std::move(p);
2071 }
2072
2073 Array<LexPoly> basis = LexPoly::groebner_basis(gens);
2074
2075 for (size_t g = 0; g < gens.size(); ++g)
2076 EXPECT_TRUE(gens(g).reduce_modulo(basis).is_zero())
2077 << "Generator " << g << " did not reduce to zero in trial " << trial;
2078
2080 }
2081}
2082
2089
2096
2097// ---
2098// ideal_member tests (6)
2099// ---
2100
2102{
2103 // f in ideal generated by itself
2104 auto x = MultiPolynomial::variable(1, 0);
2105 MultiPolynomial f = x * x + x;
2106
2108
2110}
2111
2113{
2114 // x not in ideal generated by (x^2 + 1) over double
2115 auto x = MultiPolynomial::variable(1, 0);
2116 MultiPolynomial f = x;
2117 MultiPolynomial g = x * x + MultiPolynomial(1, 1.0);
2118
2120
2122}
2123
2125{
2126 // Zero polynomial is always in the ideal
2127 MultiPolynomial zero;
2128 auto x = MultiPolynomial::variable(1, 0);
2129
2131
2133}
2134
2136{
2137 // f = xy + x + y + 1, generators = [x + 1, y + 1]
2138 // f = (x + 1) * (y + 1) so it's in the ideal
2139 auto x = MultiPolynomial::variable(2, 0);
2140 auto y = MultiPolynomial::variable(2, 1);
2141
2142 MultiPolynomial f = x * y + x + y + MultiPolynomial(2, 1.0);
2144 gens(0) = x + MultiPolynomial(2, 1.0);
2145 gens(1) = y + MultiPolynomial(2, 1.0);
2146
2148}
2149
2151{
2152 // Test helper: divides_index(alpha, beta)
2153 // Manually check internal behavior by membership
2154 auto x = MultiPolynomial::variable(1, 0);
2155 MultiPolynomial f = x * x;
2157
2158 // x^2 is divisible by x in the ideal
2160}
2161
2163{
2164 // x not divisible by x^2
2165 auto x = MultiPolynomial::variable(1, 0);
2166 MultiPolynomial f = x;
2167 Array<MultiPolynomial> gens(1, x * x);
2168
2170}
2171
2172// ---
2173// reduce_modulo tests (3)
2174// ---
2175
2177{
2178 // f = xy, divisor = xy -> remainder 0
2179 auto x = MultiPolynomial::variable(2, 0);
2180 auto y = MultiPolynomial::variable(2, 1);
2181 MultiPolynomial f = x * y;
2182
2184
2186 EXPECT_TRUE(r.is_zero());
2187}
2188
2190{
2191 // f = x^2 + 1, divisor = x -> remainder 1
2192 auto x = MultiPolynomial::variable(1, 0);
2193 MultiPolynomial f = x * x + MultiPolynomial(1, 1.0);
2194
2196
2198 EXPECT_FALSE(r.is_zero());
2199 EXPECT_EQ(r.num_terms(), 1u);
2200 EXPECT_DOUBLE_EQ(r.coeff_at(Idx{0}), 1.0);
2201}
2202
2204{
2205 // Reducing twice should give the same result
2206 auto x = MultiPolynomial::variable(1, 0);
2207 MultiPolynomial f = x * x + x + MultiPolynomial(1, 1.0);
2208
2210
2213
2214 EXPECT_EQ(r1.num_terms(), r2.num_terms());
2215 r1.for_each_term([&](const Idx& idx, double c) {
2216 EXPECT_NEAR(r2.coeff_at(idx), c, 1e-14);
2217 });
2218}
2219
2220// ---
2221// reduced_groebner_basis tests (4)
2222// ---
2223
2225{
2226 // Single generator: basis is itself (up to monic)
2227 auto x = MultiPolynomial::variable(1, 0);
2228 Array<MultiPolynomial> gens(1, 2.0 * x);
2229
2231
2232 EXPECT_GE(basis.size(), 1u);
2233 EXPECT_DOUBLE_EQ(basis(0).leading_coeff(), 1.0); // Should be monic
2234}
2235
2237{
2238 // Linear ideal: x + 1, y - 2
2239 auto x = MultiPolynomial::variable(2, 0);
2240 auto y = MultiPolynomial::variable(2, 1);
2241
2243 gens(0) = x + MultiPolynomial(2, 1.0);
2244 gens(1) = y - MultiPolynomial(2, 2.0);
2245
2247
2248 // All elements should be monic
2249 for (size_t i = 0; i < basis.size(); ++i)
2250 EXPECT_NEAR(basis(i).leading_coeff(), 1.0, 1e-12);
2251}
2252
2254{
2255 // Check that output is monic
2256 auto x = MultiPolynomial::variable(1, 0);
2257 Array<MultiPolynomial> gens(1, 3.0 * x * x + MultiPolynomial(1, 2.0));
2258
2260
2261 EXPECT_FALSE(basis.is_empty());
2262 for (size_t i = 0; i < basis.size(); ++i)
2263 if (not basis(i).is_zero())
2264 EXPECT_NEAR(basis(i).leading_coeff(), 1.0, 1e-12);
2265}
2266
2268{
2269 auto x = MultiPolynomial::variable(2, 0);
2270 auto y = MultiPolynomial::variable(2, 1);
2271
2273 gens(0) = 2.0 * x + y;
2274 gens(1) = x;
2275
2277
2278 ASSERT_EQ(basis.size(), 2u);
2279 for (size_t i = 0; i < basis.size(); ++i)
2280 EXPECT_NEAR(basis(i).leading_coeff(), 1.0, 1e-12);
2281
2282 EXPECT_TRUE((2.0 * x + y).reduce_modulo(basis).is_zero());
2283 EXPECT_TRUE(x.reduce_modulo(basis).is_zero());
2284 EXPECT_TRUE(y.reduce_modulo(basis).is_zero());
2285}
2286
2288{
2290
2291 auto x = LexPoly::variable(3, 0);
2292 auto y = LexPoly::variable(3, 1);
2293 auto z = LexPoly::variable(3, 2);
2294
2296 gens(0) = x * y - LexPoly(3, 1.0);
2297 gens(1) = y - z;
2298 gens(2) = x - LexPoly(3, 1.0);
2299
2300 Array<LexPoly> basis = LexPoly::reduced_groebner_basis(gens);
2301
2302 ASSERT_EQ(basis.size(), 3u);
2303
2305 expected(0) = x - LexPoly(3, 1.0);
2306 expected(1) = y - LexPoly(3, 1.0);
2307 expected(2) = z - LexPoly(3, 1.0);
2308
2309 for (size_t i = 0; i < expected.size(); ++i)
2310 EXPECT_TRUE(expected(i).reduce_modulo(basis).is_zero());
2311
2312 for (size_t i = 0; i < basis.size(); ++i)
2313 EXPECT_TRUE(basis(i).reduce_modulo(expected).is_zero());
2314}
2315
2317{
2319
2320 auto x = LexPoly::variable(4, 0);
2321 auto y = LexPoly::variable(4, 1);
2322 auto z = LexPoly::variable(4, 2);
2323 auto w = LexPoly::variable(4, 3);
2324
2326 gens(0) = x * y - LexPoly(4, 1.0);
2327 gens(1) = y - z;
2328 gens(2) = z - w;
2329 gens(3) = x - LexPoly(4, 1.0);
2330
2331 Array<LexPoly> basis = LexPoly::reduced_groebner_basis(gens);
2332
2333 ASSERT_EQ(basis.size(), 4u);
2334
2336 expected(0) = x - LexPoly(4, 1.0);
2337 expected(1) = y - LexPoly(4, 1.0);
2338 expected(2) = z - LexPoly(4, 1.0);
2339 expected(3) = w - LexPoly(4, 1.0);
2340
2341 for (size_t i = 0; i < expected.size(); ++i)
2342 EXPECT_TRUE(expected(i).reduce_modulo(basis).is_zero());
2343
2344 for (size_t i = 0; i < basis.size(); ++i)
2345 EXPECT_TRUE(basis(i).reduce_modulo(expected).is_zero());
2346}
2347
2349{
2351
2352 auto x = LexPoly::variable(5, 0);
2353 auto y = LexPoly::variable(5, 1);
2354 auto z = LexPoly::variable(5, 2);
2355 auto w = LexPoly::variable(5, 3);
2356 auto u = LexPoly::variable(5, 4);
2357
2359 gens(0) = x - LexPoly(5, 1.0);
2360 gens(1) = y - x;
2361 gens(2) = z - y;
2362 gens(3) = w - z;
2363 gens(4) = u - w;
2364 gens(5) = x * y - LexPoly(5, 1.0);
2365 gens(6) = y * z - LexPoly(5, 1.0);
2366 gens(7) = z * w - LexPoly(5, 1.0);
2367
2368 // Functional correctness check (timing moved to benchmark suite)
2369 Array<LexPoly> basis = LexPoly::reduced_groebner_basis(gens);
2370
2371 ASSERT_EQ(basis.size(), 5u);
2372
2374 expected(0) = x - LexPoly(5, 1.0);
2375 expected(1) = y - LexPoly(5, 1.0);
2376 expected(2) = z - LexPoly(5, 1.0);
2377 expected(3) = w - LexPoly(5, 1.0);
2378 expected(4) = u - LexPoly(5, 1.0);
2379
2380 for (size_t i = 0; i < expected.size(); ++i)
2381 EXPECT_TRUE(expected(i).reduce_modulo(basis).is_zero());
2382
2383 for (size_t i = 0; i < basis.size(); ++i)
2384 EXPECT_TRUE(basis(i).reduce_modulo(expected).is_zero());
2385}
2386
2388{
2389 // Same ideal membership before and after reduction
2390 auto x = MultiPolynomial::variable(2, 0);
2391 auto y = MultiPolynomial::variable(2, 1);
2392
2394 gens(0) = 2.0 * (x + MultiPolynomial(2, 1.0));
2395 gens(1) = 3.0 * (y - MultiPolynomial(2, 2.0));
2396
2397 MultiPolynomial f = x + y;
2398
2402
2404}
2405
2406// ===================================================================
2407// Layer 4 — Ideal Arithmetic Tests
2408// ===================================================================
2409
2410// ---
2411// ideal_sum tests (5)
2412// ---
2413
2426
2442
2444{
2445 auto x = MultiPolynomial::variable(1, 0);
2446 Array<MultiPolynomial> I(1, x * x);
2447 Array<MultiPolynomial> J(1, x * x * x);
2448
2450
2451 // Both x^2 and x^3 belong
2454}
2455
2457{
2458 auto x = MultiPolynomial::variable(1, 0);
2459 auto y = MultiPolynomial::variable(2, 0);
2460
2463
2464 EXPECT_THROW(MultiPolynomial::ideal_sum(I, J), std::invalid_argument);
2465}
2466
2476
2477// ---
2478// ideal_product tests (5)
2479// ---
2480
2482{
2483 auto x = MultiPolynomial::variable(1, 0);
2484
2487
2489
2490 EXPECT_EQ(IJ.size(), 1u);
2491 // x^2 should be in the product
2493}
2494
2496{
2497 auto x = MultiPolynomial::variable(1, 0);
2498
2501
2503
2504 EXPECT_EQ(prod.size(), 1u);
2505 // Product with ⟨1⟩ is I itself
2507}
2508
2510{
2511 auto x = MultiPolynomial::variable(1, 0);
2512
2514 Array<MultiPolynomial> J(1, x * x);
2515
2517
2518 // IJ should contain x^3
2520}
2521
2523{
2524 auto x = MultiPolynomial::variable(1, 0);
2525 auto y = MultiPolynomial::variable(2, 0);
2526
2529
2530 EXPECT_THROW(MultiPolynomial::ideal_product(I, J), std::invalid_argument);
2531}
2532
2542
2543// ---
2544// ideal_power tests (5)
2545// ---
2546
2548{
2549 auto x = MultiPolynomial::variable(1, 0);
2551
2553
2554 EXPECT_EQ(I0.size(), 1u);
2555 // I^0 = ⟨1⟩, everything is in it
2558}
2559
2570
2583
2595
2601
2602// ---
2603// contains_ideal tests (5)
2604// ---
2605
2613
2615{
2616 auto x = MultiPolynomial::variable(1, 0);
2618 Array<MultiPolynomial> J(1, x * x);
2619
2620 // I ⊇ J because J = ⟨x^2⟩ and x | x^2, so x^2 ∈ ⟨x⟩
2622}
2623
2625{
2626 auto x = MultiPolynomial::variable(1, 0);
2629
2630 // ⟨x⟩ does not contain ⟨1⟩
2632}
2633
2635{
2636 auto x = MultiPolynomial::variable(2, 0);
2637 auto y = MultiPolynomial::variable(2, 1);
2638
2640 I(0) = x;
2641 I(1) = x * y;
2642
2644
2645 // ⟨x, xy⟩ ⊇ ⟨x⟩ trivially
2647}
2648
2658
2659// ---
2660// ideals_equal tests (5)
2661// ---
2662
2670
2672{
2673 auto x = MultiPolynomial::variable(1, 0);
2676 J(0) = x;
2677 J(1) = x * x;
2678
2679 // ⟨x⟩ = ⟨x, x^2⟩ in the polynomial ring
2681}
2682
2684{
2685 auto x = MultiPolynomial::variable(1, 0);
2687 Array<MultiPolynomial> J(1, x * x);
2688
2689 // ⟨x⟩ ≠ ⟨x^2⟩
2691}
2692
2694{
2695 auto x = MultiPolynomial::variable(1, 0);
2698 J(0) = x * x - MultiPolynomial(1, 1.0);
2699 J(1) = x - MultiPolynomial(1, 1.0);
2700
2701 // ⟨x−1⟩ = ⟨x^2−1, x−1⟩ since x^2−1 = (x−1)(x+1) ∈ ⟨x−1⟩
2703}
2704
2714
2715// ---
2716// radical_member tests (7)
2717// ---
2718
2726
2728{
2729 auto x = MultiPolynomial::variable(1, 0);
2731
2732 // x ∈ √⟨x⟩ because x ∈ ⟨x⟩ trivially
2734}
2735
2737{
2738 auto x = MultiPolynomial::variable(1, 0);
2739 Array<MultiPolynomial> gens(1, x * x);
2740
2741 // x ∈ √⟨x^2⟩ because x^2 ∈ ⟨x^2⟩
2743}
2744
2746{
2747 auto x = MultiPolynomial::variable(2, 0);
2748 auto y = MultiPolynomial::variable(2, 1);
2749 Array<MultiPolynomial> gens(1, x * x);
2750
2751 // y ∉ √⟨x^2⟩ (y is independent)
2753}
2754
2756{
2757 auto x = MultiPolynomial::variable(1, 0);
2758 Array<MultiPolynomial> gens(1, x * x * x);
2759
2760 // x ∈ √⟨x^3⟩
2762}
2763
2765{
2766 auto x = MultiPolynomial::variable(2, 0);
2767 auto y = MultiPolynomial::variable(2, 1);
2768 Array<MultiPolynomial> gens(1, x * x);
2769
2770 // x ∈ √⟨x^2⟩ because (x)^2 = x^2 is in the ideal
2772 // y ∉ √⟨x^2⟩ because y^k ∉ ⟨x^2⟩ for any k (independent variable)
2774}
2775
2783
2784// ===================================================================
2785// Layer 6 — Multivariate Factorization
2786// ===================================================================
2787
2790
2791// -----------------------------------------------------------------
2792// Content
2793// -----------------------------------------------------------------
2794
2796{
2797 IntMPoly p(2);
2798 EXPECT_EQ(p.content(), 0LL);
2799}
2800
2802{
2803 IntMPoly p(2, 6LL);
2804 EXPECT_EQ(p.content(), 6LL);
2805}
2806
2808{
2809 IntMPoly p(2, -6LL);
2810 // Leading coeff is negative, so content follows that sign
2811 EXPECT_EQ(p.content(), -6LL);
2812}
2813
2815{
2816 // 6x^2 + 4x + 2 -> content = 2
2817 IntMPoly p(1);
2818 p.add_to_coeff(Idx({2}), 6LL);
2819 p.add_to_coeff(Idx({1}), 4LL);
2820 p.add_to_coeff(Idx({0}), 2LL);
2821 EXPECT_EQ(p.content(), 2LL);
2822}
2823
2825{
2826 // 6xy + 9x + 12y -> content = gcd(6, 9, 12) = 3
2827 IntMPoly p(2);
2828 p.add_to_coeff(Idx({1, 1}), 6LL);
2829 p.add_to_coeff(Idx({1, 0}), 9LL);
2830 p.add_to_coeff(Idx({0, 1}), 12LL);
2831 EXPECT_EQ(p.content(), 3LL);
2832}
2833
2835{
2836 // x + y -> content = 1
2837 IntMPoly p(2);
2838 p.add_to_coeff(Idx({1, 0}), 1LL);
2839 p.add_to_coeff(Idx({0, 1}), 1LL);
2840 EXPECT_EQ(p.content(), 1LL);
2841}
2842
2844{
2845 // 4x - 6y -> content = 2 (sign follows leading coeff)
2846 IntMPoly p(2);
2847 p.add_to_coeff(Idx({1, 0}), 4LL);
2848 p.add_to_coeff(Idx({0, 1}), -6LL);
2849 auto c = p.content();
2850 EXPECT_EQ(std::abs(c), 2LL);
2851}
2852
2853// -----------------------------------------------------------------
2854// Primitive Part
2855// -----------------------------------------------------------------
2856
2858{
2859 IntMPoly p(2);
2861 EXPECT_TRUE(pp.is_zero());
2862}
2863
2865{
2866 // x + y -> already primitive
2867 IntMPoly p(2);
2868 p.add_to_coeff(Idx({1, 0}), 1LL);
2869 p.add_to_coeff(Idx({0, 1}), 1LL);
2871 EXPECT_EQ(pp.coeff_at(Idx({1, 0})), 1LL);
2872 EXPECT_EQ(pp.coeff_at(Idx({0, 1})), 1LL);
2873}
2874
2876{
2877 // 6xy + 9x + 12y -> pp = 2xy + 3x + 4y
2878 IntMPoly p(2);
2879 p.add_to_coeff(Idx({1, 1}), 6LL);
2880 p.add_to_coeff(Idx({1, 0}), 9LL);
2881 p.add_to_coeff(Idx({0, 1}), 12LL);
2883 EXPECT_EQ(pp.coeff_at(Idx({1, 1})), 2LL);
2884 EXPECT_EQ(pp.coeff_at(Idx({1, 0})), 3LL);
2885 EXPECT_EQ(pp.coeff_at(Idx({0, 1})), 4LL);
2886}
2887
2889{
2890 // After taking primitive part, content should be 1
2891 IntMPoly p(2);
2892 p.add_to_coeff(Idx({2, 0}), 10LL);
2893 p.add_to_coeff(Idx({1, 1}), 15LL);
2894 p.add_to_coeff(Idx({0, 2}), 20LL);
2896 EXPECT_EQ(pp.content(), 1LL);
2897}
2898
2900{
2901 // -4x^2 - 6x -> content = -2, pp = 2x^2 + 3x
2902 IntMPoly p(1);
2903 p.add_to_coeff(Idx({2}), -4LL);
2904 p.add_to_coeff(Idx({1}), -6LL);
2906 // Leading coefficient of pp should be positive
2907 EXPECT_GT(pp.leading_coeff(), 0LL);
2908 EXPECT_EQ(pp.content(), 1LL);
2909}
2910
2911// -----------------------------------------------------------------
2912// Homomorphic Evaluation
2913// -----------------------------------------------------------------
2914
2916{
2917 // f(x, y) = x^2 + 2xy + y^2 = (x+y)^2
2918 // Evaluate y = 3: f(x, 3) = x^2 + 6x + 9
2919 IntMPoly f(2);
2920 f.add_to_coeff(Idx({2, 0}), 1LL);
2921 f.add_to_coeff(Idx({1, 1}), 2LL);
2922 f.add_to_coeff(Idx({0, 2}), 1LL);
2923
2924 Array<long long> pts(1, 3LL);
2925 IntPoly g = f.homomorphic_eval(0, pts);
2926
2927 EXPECT_EQ(g.get_coeff(2), 1LL); // x^2
2928 EXPECT_EQ(g.get_coeff(1), 6LL); // 6x
2929 EXPECT_EQ(g.get_coeff(0), 9LL); // 9
2930}
2931
2933{
2934 // f(x, y) = x^2 + 2xy + y^2
2935 // Keep y, evaluate x = 1: f(1, y) = 1 + 2y + y^2
2936 IntMPoly f(2);
2937 f.add_to_coeff(Idx({2, 0}), 1LL);
2938 f.add_to_coeff(Idx({1, 1}), 2LL);
2939 f.add_to_coeff(Idx({0, 2}), 1LL);
2940
2941 Array<long long> pts(1, 1LL);
2942 IntPoly g = f.homomorphic_eval(1, pts);
2943
2944 EXPECT_EQ(g.get_coeff(2), 1LL);
2945 EXPECT_EQ(g.get_coeff(1), 2LL);
2946 EXPECT_EQ(g.get_coeff(0), 1LL);
2947}
2948
2950{
2951 // f(x, y) = x^2 + xy + y
2952 // Evaluate y = 0: f(x, 0) = x^2
2953 IntMPoly f(2);
2954 f.add_to_coeff(Idx({2, 0}), 1LL);
2955 f.add_to_coeff(Idx({1, 1}), 1LL);
2956 f.add_to_coeff(Idx({0, 1}), 1LL);
2957
2958 Array<long long> pts(1, 0LL);
2959 IntPoly g = f.homomorphic_eval(0, pts);
2960
2961 EXPECT_EQ(g.get_coeff(2), 1LL);
2962 EXPECT_EQ(g.get_coeff(1), 0LL);
2963 EXPECT_EQ(g.get_coeff(0), 0LL);
2964}
2965
2967{
2968 // f(x, y, z) = x + y + z
2969 // Keep x, evaluate y=2, z=3: f(x, 2, 3) = x + 5
2970 IntMPoly f(3);
2971 f.add_to_coeff(Idx({1, 0, 0}), 1LL);
2972 f.add_to_coeff(Idx({0, 1, 0}), 1LL);
2973 f.add_to_coeff(Idx({0, 0, 1}), 1LL);
2974
2975 Array<long long> pts(2, 0LL);
2976 pts(0) = 2LL;
2977 pts(1) = 3LL;
2978 IntPoly g = f.homomorphic_eval(0, pts);
2979
2980 EXPECT_EQ(g.get_coeff(1), 1LL);
2981 EXPECT_EQ(g.get_coeff(0), 5LL);
2982}
2983
2985{
2986 IntMPoly f(2, 1LL);
2987 Array<long long> pts(1, 0LL);
2988 EXPECT_THROW(f.homomorphic_eval(2, pts), std::domain_error);
2989}
2990
2992{
2993 IntMPoly f(3, 1LL);
2994 Array<long long> pts(1, 0LL); // should be 2
2995 EXPECT_THROW(f.homomorphic_eval(0, pts), std::domain_error);
2996}
2997
2999{
3000 // f(x, y) = 3x^2y + 2x - y + 5
3001 IntMPoly f(2);
3002 f.add_to_coeff(Idx({2, 1}), 3LL);
3003 f.add_to_coeff(Idx({1, 0}), 2LL);
3004 f.add_to_coeff(Idx({0, 1}), -1LL);
3005 f.add_to_coeff(Idx({0, 0}), 5LL);
3006
3007 // Evaluate y = 2: f(x, 2) = 6x^2 + 2x - 2 + 5 = 6x^2 + 2x + 3
3008 Array<long long> pts(1, 2LL);
3009 IntPoly g = f.homomorphic_eval(0, pts);
3010
3011 EXPECT_EQ(g.get_coeff(2), 6LL);
3012 EXPECT_EQ(g.get_coeff(1), 2LL);
3013 EXPECT_EQ(g.get_coeff(0), 3LL);
3014
3015 // Verify by evaluating at x = 1: f(1, 2) = 6 + 2 + 3 = 11
3016 EXPECT_EQ(g(1LL), 11LL);
3017
3018 // Cross-check with multivariate eval
3019 Array<long long> pt2(2, 0LL);
3020 pt2(0) = 1LL;
3021 pt2(1) = 2LL;
3022 EXPECT_EQ(f.eval(pt2), 11LL);
3023}
3024
3025// -----------------------------------------------------------------
3026// Factor Recombination
3027// -----------------------------------------------------------------
3028
3030{
3031 // f = x + 1, candidates = {x + 1}
3032 IntMPoly f(1);
3033 f.add_to_coeff(Idx({1}), 1LL);
3034 f.add_to_coeff(Idx({0}), 1LL);
3035
3036 Array<IntMPoly> cands(1, f);
3037 auto result = IntMPoly::factor_recombination(f, cands);
3038
3039 size_t count = 0;
3040 for (auto it = result.get_it(); it.has_curr(); it.next_ne())
3041 ++count;
3042 EXPECT_EQ(count, 1u);
3043}
3044
3046{
3047 // f = (x + 1)(x - 1) = x^2 - 1
3048 IntMPoly f1(1);
3049 f1.add_to_coeff(Idx({1}), 1LL);
3050 f1.add_to_coeff(Idx({0}), 1LL);
3051
3052 IntMPoly f2(1);
3053 f2.add_to_coeff(Idx({1}), 1LL);
3054 f2.add_to_coeff(Idx({0}), -1LL);
3055
3056 IntMPoly f = f1 * f2;
3057
3059 cands(0) = f1;
3060 cands(1) = f2;
3061
3062 auto result = IntMPoly::factor_recombination(f, cands);
3063
3064 // Should find both factors
3065 size_t count = 0;
3066 for (auto it = result.get_it(); it.has_curr(); it.next_ne())
3067 ++count;
3068 EXPECT_GE(count, 1u);
3069 EXPECT_LE(count, 2u);
3070
3071 // Product of result factors should equal f (up to constant)
3072 IntMPoly product(1, 1LL);
3073 for (auto it = result.get_it(); it.has_curr(); it.next_ne())
3074 product = product * it.get_curr().factor;
3075 EXPECT_TRUE((product - f).is_zero());
3076}
3077
3078// -----------------------------------------------------------------
3079// Factorize (main method)
3080// -----------------------------------------------------------------
3081
3083{
3084 IntMPoly p(2);
3085 auto factors = p.factorize();
3086 EXPECT_TRUE(factors.is_empty());
3087}
3088
3090{
3091 IntMPoly p(2, 5LL);
3092 auto factors = p.factorize();
3093 EXPECT_TRUE(factors.is_empty());
3094}
3095
3097{
3098 // f(x, y) = 2x + 3y -> irreducible (should return as one factor)
3099 IntMPoly f(2);
3100 f.add_to_coeff(Idx({1, 0}), 2LL);
3101 f.add_to_coeff(Idx({0, 1}), 3LL);
3102
3103 auto factors = f.factorize();
3104 // Should have at least 1 factor
3105 size_t count = 0;
3106 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3107 ++count;
3108 EXPECT_GE(count, 1u);
3109}
3110
3112{
3113 auto x = IntMPoly::variable(2, 0);
3114 auto y = IntMPoly::variable(2, 1);
3115
3116 IntMPoly f1 = x * x + 2LL * y + IntMPoly(2, 1LL);
3117 IntMPoly f2 = x * x + 2LL * y + IntMPoly(2, 3LL);
3118 IntMPoly f = f1 * f2;
3119
3121 auto [q, r] = f.divmod(divisors);
3122
3123 EXPECT_TRUE(r.is_zero());
3124 EXPECT_TRUE((q(0) - f2).is_zero());
3125}
3126
3128{
3129 // f(x) = x^2 - 1 = (x-1)(x+1) in 1 variable
3130 IntMPoly f(1);
3131 f.add_to_coeff(Idx({2}), 1LL);
3132 f.add_to_coeff(Idx({0}), -1LL);
3133
3134 auto factors = f.factorize();
3135
3136 // Product of factors should reproduce f (up to sign/constant)
3137 IntMPoly product(1, 1LL);
3138 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3139 {
3140 const auto &ft = it.get_curr();
3141 for (size_t m = 0; m < ft.multiplicity; ++m)
3142 product = product * ft.factor;
3143 }
3144
3145 // Check that product evaluates the same as f at several points
3146 for (long long v = -3; v <= 3; ++v)
3147 {
3148 Array<long long> pt(1, v);
3149 EXPECT_EQ(product.eval(pt), f.eval(pt))
3150 << "Mismatch at x = " << v;
3151 }
3152}
3153
3155{
3156 auto x = IntMPoly::variable(2, 0);
3157 IntMPoly f = x * x - IntMPoly(2, 1LL);
3158
3159 auto factors = f.factorize();
3160
3161 IntMPoly product(2, 1LL);
3162 size_t count = 0;
3163 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3164 {
3165 const auto &ft = it.get_curr();
3166 ++count;
3167 for (size_t m = 0; m < ft.multiplicity; ++m)
3168 product = product * ft.factor;
3169 }
3170
3171 EXPECT_EQ(count, 2u);
3172 EXPECT_TRUE((product - f).is_zero());
3173}
3174
3176{
3177 auto x = IntMPoly::variable(2, 0);
3178 IntMPoly f = x * x + 2LL * x + IntMPoly(2, 1LL);
3179
3180 auto factors = f.factorize();
3181
3182 ASSERT_FALSE(factors.is_empty());
3183 auto it = factors.get_it();
3184 ASSERT_TRUE(it.has_curr());
3185 const auto &ft = it.get_curr();
3186 EXPECT_EQ(ft.multiplicity, 2u);
3187 EXPECT_TRUE((ft.factor - (x + IntMPoly(2, 1LL))).is_zero());
3188 it.next_ne();
3189 EXPECT_FALSE(it.has_curr());
3190}
3191
3193{
3194 auto x = IntMPoly::variable(2, 0);
3195 IntMPoly f = 6LL * (x * x - IntMPoly(2, 1LL));
3196
3197 auto factors = f.factorize();
3198
3199 IntMPoly product(2, 1LL);
3200 bool found_content = false;
3201 size_t count = 0;
3202 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3203 {
3204 const auto &ft = it.get_curr();
3205 ++count;
3206 if (ft.multiplicity == 1 and ft.factor.is_constant() and ft.factor.leading_coeff() == 6LL)
3207 found_content = true;
3208 for (size_t m = 0; m < ft.multiplicity; ++m)
3209 product = product * ft.factor;
3210 }
3211
3212 EXPECT_EQ(count, 3u);
3214 EXPECT_TRUE((product - f).is_zero());
3215}
3216
3218{
3219 auto x = IntMPoly::variable(2, 0);
3220 IntMPoly f = -((x + IntMPoly(2, 1LL)) * (x - IntMPoly(2, 2LL)));
3221
3222 auto factors = f.factorize();
3223
3224 IntMPoly product(2, 1LL);
3225 bool found_minus_one = false;
3226 size_t count = 0;
3227 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3228 {
3229 const auto &ft = it.get_curr();
3230 ++count;
3231 if (ft.multiplicity == 1 and ft.factor.is_constant() and ft.factor.leading_coeff() == -1LL)
3232 found_minus_one = true;
3233 for (size_t m = 0; m < ft.multiplicity; ++m)
3234 product = product * ft.factor;
3235 }
3236
3237 EXPECT_EQ(count, 3u);
3239 EXPECT_TRUE((product - f).is_zero());
3240}
3241
3243{
3244 // f(x) = (x - 1)(x - 2)(x - 3) = x^3 - 6x^2 + 11x - 6
3245 IntMPoly f(1);
3246 f.add_to_coeff(Idx({3}), 1LL);
3247 f.add_to_coeff(Idx({2}), -6LL);
3248 f.add_to_coeff(Idx({1}), 11LL);
3249 f.add_to_coeff(Idx({0}), -6LL);
3250
3251 auto factors = f.factorize();
3252
3253 // Product of factors should reproduce f
3254 IntMPoly product(1, 1LL);
3255 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3256 {
3257 const auto &ft = it.get_curr();
3258 for (size_t m = 0; m < ft.multiplicity; ++m)
3259 product = product * ft.factor;
3260 }
3261
3262 for (long long v = -2; v <= 5; ++v)
3263 {
3264 Array<long long> pt(1, v);
3265 EXPECT_EQ(product.eval(pt), f.eval(pt))
3266 << "Mismatch at x = " << v;
3267 }
3268}
3269
3271{
3272 // f(x) = 6x^2 - 6 = 6(x^2 - 1) = 6(x-1)(x+1)
3273 IntMPoly f(1);
3274 f.add_to_coeff(Idx({2}), 6LL);
3275 f.add_to_coeff(Idx({0}), -6LL);
3276
3277 EXPECT_EQ(f.content(), 6LL);
3278
3280 EXPECT_EQ(pp.content(), 1LL);
3281
3282 auto factors = pp.factorize();
3283 // Should factorize the primitive part
3284 EXPECT_FALSE(factors.is_empty());
3285}
3286
3288{
3289 auto x = IntMPoly::variable(2, 0);
3290 auto y = IntMPoly::variable(2, 1);
3291 IntMPoly f = 6LL * (x * x - y * y);
3292
3293 auto factors = f.factorize();
3294
3295 IntMPoly product(2, 1LL);
3296 bool found_content = false;
3297 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3298 {
3299 const auto &ft = it.get_curr();
3300 if (ft.multiplicity == 1 and ft.factor.is_constant() and ft.factor.leading_coeff() == 6LL)
3301 found_content = true;
3302 for (size_t m = 0; m < ft.multiplicity; ++m)
3303 product = product * ft.factor;
3304 }
3305
3307 EXPECT_TRUE((product - f).is_zero());
3308}
3309
3311{
3312 auto x = IntMPoly::variable(2, 0);
3313 auto y = IntMPoly::variable(2, 1);
3314 IntMPoly f = -((x + y) * (x - y));
3315
3316 auto factors = f.factorize();
3317
3318 IntMPoly product(2, 1LL);
3319 bool found_minus_one = false;
3320 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3321 {
3322 const auto &ft = it.get_curr();
3323 if (ft.multiplicity == 1 and ft.factor.is_constant() and ft.factor.leading_coeff() == -1LL)
3324 found_minus_one = true;
3325 for (size_t m = 0; m < ft.multiplicity; ++m)
3326 product = product * ft.factor;
3327 }
3328
3330 EXPECT_TRUE((product - f).is_zero());
3331}
3332
3334{
3335 // f(x, y) = (x + y)(x - y) = x^2 - y^2
3336 auto x = IntMPoly::variable(2, 0);
3337 auto y = IntMPoly::variable(2, 1);
3338 IntMPoly f = x * x - y * y;
3339
3340 auto factors = f.factorize();
3341
3342 IntMPoly product(2, 1LL);
3343 bool found_x_minus_y = false;
3344 bool found_x_plus_y = false;
3345 size_t count = 0;
3346 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3347 {
3348 const auto &ft = it.get_curr();
3349 ++count;
3350 if (ft.multiplicity == 1 and (ft.factor - (x - y)).is_zero())
3351 found_x_minus_y = true;
3352 if (ft.multiplicity == 1 and (ft.factor - (x + y)).is_zero())
3353 found_x_plus_y = true;
3354 for (size_t m = 0; m < ft.multiplicity; ++m)
3355 product = product * ft.factor;
3356 }
3357
3358 EXPECT_EQ(count, 2u);
3361 EXPECT_TRUE((product - f).is_zero());
3362}
3363
3365{
3366 auto x = IntMPoly::variable(2, 0);
3367 auto y = IntMPoly::variable(2, 1);
3368 IntMPoly f = (x + y) * (x + y);
3369
3370 auto factors = f.factorize();
3371
3372 ASSERT_FALSE(factors.is_empty());
3373 auto it = factors.get_it();
3374 ASSERT_TRUE(it.has_curr());
3375 const auto &ft = it.get_curr();
3376 EXPECT_EQ(ft.multiplicity, 2u);
3377 EXPECT_TRUE((ft.factor - (x + y)).is_zero());
3378 it.next_ne();
3379 EXPECT_FALSE(it.has_curr());
3380}
3381
3383{
3384 auto x = IntMPoly::variable(3, 0);
3385 auto y = IntMPoly::variable(3, 1);
3386 auto z = IntMPoly::variable(3, 2);
3387
3388 IntMPoly f1 = x + y + z + IntMPoly(3, 1LL);
3389 IntMPoly f2 = x - 2LL * y + z - IntMPoly(3, 3LL);
3390 IntMPoly f = f1 * f2;
3391
3392 auto factors = f.factorize();
3393
3394 IntMPoly product(3, 1LL);
3395 bool found_f1 = false;
3396 bool found_f2 = false;
3397 size_t count = 0;
3398 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3399 {
3400 const auto &ft = it.get_curr();
3401 ++count;
3402 if (ft.multiplicity == 1 and (ft.factor - f1).is_zero())
3403 found_f1 = true;
3404 if (ft.multiplicity == 1 and (ft.factor - f2).is_zero())
3405 found_f2 = true;
3406 for (size_t m = 0; m < ft.multiplicity; ++m)
3407 product = product * ft.factor;
3408 }
3409
3410 EXPECT_EQ(count, 2u);
3413 EXPECT_TRUE((product - f).is_zero());
3414
3415 for (long long xv = -2; xv <= 2; ++xv)
3416 for (long long yv = -2; yv <= 2; ++yv)
3417 for (long long zv = -2; zv <= 2; ++zv)
3418 {
3419 Array<long long> pt(3, 0LL);
3420 pt(0) = xv;
3421 pt(1) = yv;
3422 pt(2) = zv;
3423 EXPECT_EQ(product.eval(pt), f.eval(pt))
3424 << "Mismatch at (" << xv << ", " << yv << ", " << zv << ")";
3425 }
3426}
3427
3429{
3430 auto x = IntMPoly::variable(2, 0);
3431 auto y = IntMPoly::variable(2, 1);
3432
3433 IntMPoly f1 = 2LL * x + 3LL * y + IntMPoly(2, 1LL);
3434 IntMPoly f2 = 4LL * x + 5LL * y + IntMPoly(2, 2LL);
3435 IntMPoly f = f1 * f2;
3436
3437 auto factors = f.factorize();
3438
3439 IntMPoly product(2, 1LL);
3440 bool found_f1 = false;
3441 bool found_f2 = false;
3442 size_t count = 0;
3443 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3444 {
3445 const auto &ft = it.get_curr();
3446 ++count;
3447 if (ft.multiplicity == 1 and (ft.factor - f1).is_zero())
3448 found_f1 = true;
3449 if (ft.multiplicity == 1 and (ft.factor - f2).is_zero())
3450 found_f2 = true;
3451 for (size_t m = 0; m < ft.multiplicity; ++m)
3452 product = product * ft.factor;
3453 }
3454
3455 EXPECT_EQ(count, 2u);
3458 EXPECT_TRUE((product - f).is_zero());
3459}
3460
3462{
3463 auto x = IntMPoly::variable(2, 0);
3464 auto y = IntMPoly::variable(2, 1);
3465
3466 IntMPoly f1 = 2LL * x * x + 3LL * y + IntMPoly(2, 1LL);
3467 IntMPoly f2 = 3LL * x * x + 5LL * y + IntMPoly(2, 2LL);
3468 IntMPoly f = f1 * f2;
3469
3470 auto factors = f.factorize();
3471
3472 IntMPoly product(2, 1LL);
3473 bool found_f1 = false;
3474 bool found_f2 = false;
3475 size_t count = 0;
3476 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3477 {
3478 const auto &ft = it.get_curr();
3479 ++count;
3480 if (ft.multiplicity == 1 and (ft.factor - f1).is_zero())
3481 found_f1 = true;
3482 if (ft.multiplicity == 1 and (ft.factor - f2).is_zero())
3483 found_f2 = true;
3484 for (size_t m = 0; m < ft.multiplicity; ++m)
3485 product = product * ft.factor;
3486 }
3487
3488 EXPECT_EQ(count, 2u);
3491 EXPECT_TRUE((product - f).is_zero());
3492}
3493
3495{
3496 auto x = IntMPoly::variable(2, 0);
3497 auto y = IntMPoly::variable(2, 1);
3498
3499 IntMPoly f1 = x * x + y + IntMPoly(2, 1LL);
3500 IntMPoly f2 = x + y * y + IntMPoly(2, 2LL);
3501 IntMPoly f = f1 * f2;
3502
3503 auto factors = f.factorize();
3504
3505 IntMPoly product(2, 1LL);
3506 bool found_f1 = false;
3507 bool found_f2 = false;
3508 size_t count = 0;
3509 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3510 {
3511 const auto &ft = it.get_curr();
3512 ++count;
3513 if (ft.multiplicity == 1 and (ft.factor - f1).is_zero())
3514 found_f1 = true;
3515 if (ft.multiplicity == 1 and (ft.factor - f2).is_zero())
3516 found_f2 = true;
3517 for (size_t m = 0; m < ft.multiplicity; ++m)
3518 product = product * ft.factor;
3519 }
3520
3521 EXPECT_EQ(count, 2u);
3524 EXPECT_TRUE((product - f).is_zero());
3525}
3526
3528{
3529 auto x = IntMPoly::variable(3, 0);
3530 auto y = IntMPoly::variable(3, 1);
3531 auto z = IntMPoly::variable(3, 2);
3532
3533 IntMPoly f1 = x * x + y * z + y + IntMPoly(3, 1LL);
3534 IntMPoly f2 = x + y + z + IntMPoly(3, 2LL);
3535 IntMPoly f = f1 * f2;
3536
3537 auto factors = f.factorize();
3538
3539 IntMPoly product(3, 1LL);
3540 bool found_f1 = false;
3541 bool found_f2 = false;
3542 size_t count = 0;
3543 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3544 {
3545 const auto &ft = it.get_curr();
3546 ++count;
3547 if (ft.multiplicity == 1 and (ft.factor - f1).is_zero())
3548 found_f1 = true;
3549 if (ft.multiplicity == 1 and (ft.factor - f2).is_zero())
3550 found_f2 = true;
3551 for (size_t m = 0; m < ft.multiplicity; ++m)
3552 product = product * ft.factor;
3553 }
3554
3555 EXPECT_EQ(count, 2u);
3558 EXPECT_TRUE((product - f).is_zero());
3559}
3560
3562{
3563 auto x = IntMPoly::variable(2, 0);
3564 auto y = IntMPoly::variable(2, 1);
3565
3566 IntMPoly f1 = x * x + 2LL * y + IntMPoly(2, 1LL);
3567 IntMPoly f2 = x * x + 2LL * y + IntMPoly(2, 3LL);
3568 IntMPoly f = f1 * f2;
3569
3570 auto factors = f.factorize();
3571
3572 IntMPoly product(2, 1LL);
3573 bool found_f1 = false;
3574 bool found_f2 = false;
3575 size_t count = 0;
3576 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3577 {
3578 const auto &ft = it.get_curr();
3579 ++count;
3580 if (ft.multiplicity == 1 and (ft.factor - f1).is_zero())
3581 found_f1 = true;
3582 if (ft.multiplicity == 1 and (ft.factor - f2).is_zero())
3583 found_f2 = true;
3584 for (size_t m = 0; m < ft.multiplicity; ++m)
3585 product = product * ft.factor;
3586 }
3587
3588 EXPECT_EQ(count, 2u);
3591 EXPECT_TRUE((product - f).is_zero());
3592}
3593
3595{
3596 auto x = IntMPoly::variable(3, 0);
3597 auto y = IntMPoly::variable(3, 1);
3598 auto z = IntMPoly::variable(3, 2);
3599
3600 IntMPoly f1 = x * x + y + 3LL * z + IntMPoly(3, 2LL);
3601 IntMPoly f2 = x * x + y + 3LL * z + IntMPoly(3, 11LL);
3602 IntMPoly f = f1 * f2;
3603
3604 auto factors = f.factorize();
3605
3606 IntMPoly product(3, 1LL);
3607 bool found_f1 = false;
3608 bool found_f2 = false;
3609 size_t count = 0;
3610 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3611 {
3612 const auto &ft = it.get_curr();
3613 ++count;
3614 if (ft.multiplicity == 1 and (ft.factor - f1).is_zero())
3615 found_f1 = true;
3616 if (ft.multiplicity == 1 and (ft.factor - f2).is_zero())
3617 found_f2 = true;
3618 for (size_t m = 0; m < ft.multiplicity; ++m)
3619 product = product * ft.factor;
3620 }
3621
3622 EXPECT_EQ(count, 2u);
3625 EXPECT_TRUE((product - f).is_zero());
3626}
3627
3629{
3630 auto x = IntMPoly::variable(2, 0);
3631 auto y = IntMPoly::variable(2, 1);
3632
3633 IntMPoly f1 = x * x * x + x + 2LL * y + IntMPoly(2, 1LL);
3634 IntMPoly f2 = x * x * x + x + 2LL * y + IntMPoly(2, 3LL);
3635 IntMPoly f = f1 * f2;
3636
3637 auto factors = f.factorize();
3638
3639 IntMPoly product(2, 1LL);
3640 bool found_f1 = false;
3641 bool found_f2 = false;
3642 size_t count = 0;
3643 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3644 {
3645 const auto &ft = it.get_curr();
3646 ++count;
3647 if (ft.multiplicity == 1 and (ft.factor - f1).is_zero())
3648 found_f1 = true;
3649 if (ft.multiplicity == 1 and (ft.factor - f2).is_zero())
3650 found_f2 = true;
3651 for (size_t m = 0; m < ft.multiplicity; ++m)
3652 product = product * ft.factor;
3653 }
3654
3655 EXPECT_EQ(count, 2u);
3658 EXPECT_TRUE((product - f).is_zero());
3659}
3660
3662{
3663 // f(x, y) = x^2 + y^2 + 1 (irreducible over Z)
3664 IntMPoly f(2);
3665 f.add_to_coeff(Idx({2, 0}), 1LL);
3666 f.add_to_coeff(Idx({0, 2}), 1LL);
3667 f.add_to_coeff(Idx({0, 0}), 1LL);
3668
3669 auto factors = f.factorize();
3670
3671 // Should return exactly 1 factor (the polynomial itself)
3672 size_t count = 0;
3673 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3674 ++count;
3675 EXPECT_GE(count, 1u);
3676
3677 // Product should match f at sample points
3678 IntMPoly product(2, 1LL);
3679 for (auto it = factors.get_it(); it.has_curr(); it.next_ne())
3680 {
3681 const auto &ft = it.get_curr();
3682 for (size_t m = 0; m < ft.multiplicity; ++m)
3683 product = product * ft.factor;
3684 }
3685
3686 for (long long xv = -2; xv <= 2; ++xv)
3687 for (long long yv = -2; yv <= 2; ++yv)
3688 {
3689 Array<long long> pt(2, 0LL);
3690 pt(0) = xv;
3691 pt(1) = yv;
3692 EXPECT_EQ(product.eval(pt), f.eval(pt));
3693 }
3694}
long double w
Definition btreepic.C:153
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:351
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:245
Sparse multivariate polynomial.
static bool radical_member(const Gen_MultiPolynomial &f, const Array< Gen_MultiPolynomial > &gens)
Test membership in radical of ideal: f ∈ √I.
static Array< Gen_MultiPolynomial > reduced_groebner_basis(const Array< Gen_MultiPolynomial > &generators)
Reduced Gröbner basis (minimized and normalized).
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.
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 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 contains_ideal(const Array< Gen_MultiPolynomial > &I_gens, const Array< Gen_MultiPolynomial > &J_gens)
Test whether ideal I contains ideal J: I ⊇ J.
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 s_poly(const Gen_MultiPolynomial &f, const Gen_MultiPolynomial &g)
S-polynomial (Sylvester polynomial) of two polynomials.
Array< Coefficient > eval_batch(const Array< Array< Coefficient > > &pts) const
Evaluate polynomial at multiple points (with parallelism support).
void to_binary(std::ostream &out) const
Serialize polynomial to binary stream.
std::string to_str(const DynList< std::string > &names=DynList< std::string >()) const
Human-readable string.
void for_each_term(Op &&op) const
Visit every non-zero term in ascending monomial order.
Array< Coefficient > eval_gradient(const Array< Coefficient > &pt) const
Evaluate gradient at a point.
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).
static Gen_MultiPolynomial monomial(size_t nvars, const Array< size_t > &idx, const Coefficient &c=Coefficient(1))
A single monomial .
size_t num_terms() const noexcept
Number of non-zero terms.
bool is_zero() const noexcept
True if this is the zero polynomial.
Coefficient content() const
Content of a multivariate polynomial: GCD of all coefficients.
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.
Array< Array< Coefficient > > eval_hessian(const Array< Coefficient > &pt) const
Evaluate Hessian at a point.
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).
Gen_MultiPolynomial promote(size_t new_nv) const
Promote to a polynomial with more variables.
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.
Gen_Polynomial< Coefficient > homomorphic_eval(size_t keep_var, const Array< Coefficient > &eval_pts) const
Homomorphic evaluation: reduce to univariate by substitution.
static DynList< FactorTerm > factor_recombination(Gen_MultiPolynomial f, const Array< Gen_MultiPolynomial > &candidates)
Factor recombination: find true factors from lifted candidates.
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.
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.
Coefficient coeff_at(const Array< size_t > &idx) const
Read coefficient at a multi-index (0 if absent).
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.
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.
std::pair< Array< Gen_MultiPolynomial >, Gen_MultiPolynomial > divmod(const Array< Gen_MultiPolynomial > &divisors) const
Multivariate division with remainder (Buchberger algorithm).
Coefficient eval(const Array< Coefficient > &pt) const
Evaluate 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⟩.
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.
Coefficient get_coeff(size_t exp) const noexcept
Coefficient accessor (read-only at exponent).
constexpr size_t size() const noexcept
Returns the number of entries in the table.
Definition hashDry.H:619
#define TEST(name)
static mt19937 rng
__gmp_expr< typename __gmp_resolve_expr< T, V >::value_type, __gmp_binary_expr< __gmp_expr< T, U >, __gmp_expr< V, W >, __gmp_pow_function > > pow(const __gmp_expr< T, U > &expr1, const __gmp_expr< V, W > &expr2)
Definition gmpfrxx.h:4061
static mpfr_t y
Definition mpfr_mul_d.c:3
Gen_MultiPolynomial< long long, Grevlex_Order > IntMPoly
static void expect_buchberger_criterion(const Array< MP > &basis)
static void expect_autoreduced_basis(const Array< MP > &basis)
Array< size_t > Idx
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
size_t size(Node *root) noexcept
Gen_MultiPolynomial< double, Grevlex_Order > MultiPolynomial
Multivariate polynomial with double coefficients, grevlex.
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
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.
T product(const Container &container, const T &init=T{1})
Compute product of all elements.
bool diff(const C1 &c1, const C2 &c2, Eq e=Eq())
Check if two containers differ.
static long & df(typename GT::Node *p)
Internal helper: DFS discovery time stored in NODE_COUNTER(p).
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
#define LL
Definition ran_array.c:24
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)
gsl_rng * r
Sparse multivariate polynomial over an arbitrary coefficient ring.