Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
polynomial-test.cpp
Go to the documentation of this file.
1/*
5 * Authors:
6 * Krzysztof KosiƄski <tweenk.pl@gmail.com>
7 *
8 * Copyright 2015-2019 Authors
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it either under the terms of the GNU Lesser General Public
12 * License version 2.1 as published by the Free Software Foundation
13 * (the "LGPL") or, at your option, under the terms of the Mozilla
14 * Public License Version 1.1 (the "MPL"). If you do not alter this
15 * notice, a recipient may use your version of this file under either
16 * the MPL or the LGPL.
17 *
18 * You should have received a copy of the LGPL along with this library
19 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * You should have received a copy of the MPL along with this library
22 * in the file COPYING-MPL-1.1
23 *
24 * The contents of this file are subject to the Mozilla Public License
25 * Version 1.1 (the "License"); you may not use this file except in
26 * compliance with the License. You may obtain a copy of the License at
27 * http://www.mozilla.org/MPL/
28 *
29 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
30 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
31 * the specific language governing rights and limitations.
32 */
33
34#include "testing.h"
35#include <array>
36#include <iostream>
37#include <glib.h>
38
39#include <2geom/polynomial.h>
40
41using namespace Geom;
42
43TEST(PolynomialTest, SolveQuadratic) {
44 for (unsigned i = 0; i < 1000; ++i) {
45 Coord x1 = g_random_double_range(-100, 100);
46 Coord x2 = g_random_double_range(-100, 100);
47
48 Coord a = g_random_double_range(-10, 10);
49 Coord b = -a * (x1 + x2);
50 Coord c = a * x1 * x2;
51
52 std::vector<Coord> result = solve_quadratic(a, b, c);
53
54 EXPECT_EQ(result.size(), 2u);
55 if (x1 < x2) {
56 EXPECT_FLOAT_EQ(result[0], x1);
57 EXPECT_FLOAT_EQ(result[1], x2);
58 } else {
59 EXPECT_FLOAT_EQ(result[0], x2);
60 EXPECT_FLOAT_EQ(result[1], x1);
61 }
62 }
63}
64
65TEST(PolynomialTest, SolvePathologicalQuadratic) {
66 std::vector<Coord> r;
67
68 r = solve_quadratic(1, -1e9, 1);
69 ASSERT_EQ(r.size(), 2u);
70 EXPECT_FLOAT_EQ(r[0], 1e-9);
71 EXPECT_FLOAT_EQ(r[1], 1e9);
72
73 r = solve_quadratic(1, -4, 3.999999);
74 ASSERT_EQ(r.size(), 2u);
75 EXPECT_FLOAT_EQ(r[0], 1.999);
76 EXPECT_FLOAT_EQ(r[1], 2.001);
77
78 r = solve_quadratic(1, 0, -4);
79 ASSERT_EQ(r.size(), 2u);
80 EXPECT_FLOAT_EQ(r[0], -2);
81 EXPECT_FLOAT_EQ(r[1], 2);
82
83 r = solve_quadratic(1, 0, -16);
84 ASSERT_EQ(r.size(), 2u);
85 EXPECT_FLOAT_EQ(r[0], -4);
86 EXPECT_FLOAT_EQ(r[1], 4);
87
88 r = solve_quadratic(1, 0, -100);
89 ASSERT_EQ(r.size(), 2u);
90 EXPECT_FLOAT_EQ(r[0], -10);
91 EXPECT_FLOAT_EQ(r[1], 10);
92}
93
94TEST(PolynomialTest, SolveCubic) {
95 for (unsigned i = 0; i < 1000; ++i) {
96 Coord x1 = g_random_double_range(-100, 100);
97 Coord x2 = g_random_double_range(-100, 100);
98 Coord x3 = g_random_double_range(-100, 100);
99
100 Coord a = g_random_double_range(-10, 10);
101 Coord b = -a * (x1 + x2 + x3);
102 Coord c = a * (x1*x2 + x2*x3 + x1*x3);
103 Coord d = -a * x1 * x2 * x3;
104
105 std::vector<Coord> result = solve_cubic(a, b, c, d);
106 std::vector<Coord> x(3); x[0] = x1; x[1] = x2; x[2] = x3;
107 std::sort(x.begin(), x.end());
108
109 ASSERT_EQ(result.size(), 3u);
110 EXPECT_FLOAT_EQ(result[0], x[0]);
111 EXPECT_FLOAT_EQ(result[1], x[1]);
112 EXPECT_FLOAT_EQ(result[2], x[2]);
113 }
114
115 // corner cases
116 // (x^2 + 7)(x - 2)
117 std::vector<Coord> r1 = solve_cubic(1, -2, 7, -14);
118 EXPECT_EQ(r1.size(), 1u);
119 EXPECT_FLOAT_EQ(r1[0], 2);
120
121 // (x + 1)^2 (x-2)
122 std::vector<Coord> r2 = solve_cubic(1, 0, -3, -2);
123 ASSERT_EQ(r2.size(), 3u);
124 EXPECT_FLOAT_EQ(r2[0], -1);
125 EXPECT_FLOAT_EQ(r2[1], -1);
126 EXPECT_FLOAT_EQ(r2[2], 2);
127}
128
130TEST(PolynomialTest, SolveQuartic_4_roots)
131{
132 g_random_set_seed(0xB737A380);
133 double const eps = 4e-6;
134 std::array<Coord, 4> roots;
135
136 for (size_t _ = 0; _ < 10'000; ++_) {
137 // Generate random but sorted roots
138 for (Coord &root : roots) {
139 root = g_random_double_range(-100, 100);
140 }
141 std::sort(roots.begin(), roots.end());
142
143 // Generate random leading coefficient
144 Coord a = 0;
145 while (a == 0) {
146 a = g_random_double_range(-100, 100);
147 }
148
149 // Generate symmetric basis polynomials in roots
150 Coord const sym1 = roots[0] + roots[1] + roots[2] + roots[3];
151 Coord const sym2 = roots[0] * roots[1] +
152 roots[0] * roots[2] +
153 roots[0] * roots[3] +
154 roots[1] * roots[2] +
155 roots[1] * roots[3] +
156 roots[2] * roots[3];
157 Coord const sym3 = roots[0] * roots[1] * roots[2] +
158 roots[0] * roots[1] * roots[3] +
159 roots[0] * roots[2] * roots[3] +
160 roots[1] * roots[2] * roots[3];
161 Coord const sym4 = roots[0] * roots[1] * roots[2] * roots[3];
162
163 // Try to recover roots from the coefficients of the polynomial
164 auto const recovered = solve_quartic(a, -a * sym1, a * sym2, -a * sym3, a * sym4);
165 ASSERT_EQ(recovered.size(), 4);
166 for (size_t i = 0; i < 4; ++i) {
167 EXPECT_TRUE(are_near(recovered[i], roots[i], eps));
168 }
169 }
170}
171
173TEST(PolynomialTest, SolveQuartic_Evaluate)
174{
175 g_random_set_seed(0xB737A380);
176 double const eps = 3e-6;
177 for (size_t _ = 0; _ < 10'000; ++_) {
179 for (size_t i = 0; i < 4; ++i) {
180 quartic.push_back(g_random_double_range(-100, 100));
181 }
182 quartic.push_back(1);
183
184 auto const roots = solve_quartic(quartic[4], quartic[3], quartic[2], quartic[1], quartic[0]);
185 for (Coord root : roots) {
186 EXPECT_TRUE(are_near(quartic.eval(root), 0, eps));
187 }
188 }
189}
190
192static std::array<Coord, 3> get_random_irreducible_quadratic()
193{
194 double a = 0;
195 while (std::abs(a) < 1e-3) {
196 a = g_random_double_range(-10, 10);
197 }
198 double b = g_random_double_range(-10, 10);
199 double c = g_random_double_range(1, 100);
200 int sign = 2 * g_random_boolean() - 1;
201 return {
202 sign * sqr(a),
203 sign * 2 * a * b,
204 sign * (sqr(b) + c)
205 };
206}
207
209TEST(PolynomialTest, SolveQuartic_2_roots)
210{
211 g_random_set_seed(0xB737A380);
212 double const eps = 1e-9;
213
214 std::array<Coord, 2> roots;
215 for (size_t _ = 0; _ < 10'000; ++_) {
216 // Generate random but sorted roots
217 for (Coord &root : roots) {
218 root = g_random_double_range(-100, 100);
219 }
220 std::sort(roots.begin(), roots.end());
221
222 // Generate symmetric polynomials
223 Coord const sym1 = roots[0] + roots[1];
224 Coord const sym2 = roots[0] * roots[1];
225 auto const comp = get_random_irreducible_quadratic();
226
227 // Try to recover roots from the coefficients of the polynomial
228 auto const recovered = solve_quartic(comp[0],
229 comp[1] - comp[0] * sym1,
230 comp[2] + sym2 * comp[0] - comp[1] * sym1,
231 comp[1] * sym2 - comp[2] * sym1,
232 comp[2] * sym2);
233 ASSERT_EQ(recovered.size(), 2);
234 for (size_t i = 0; i < 2; ++i) {
235 ASSERT_TRUE(are_near(recovered[i], roots[i], eps));
236 }
237 }
238}
239
241TEST(PolynomialTest, SolveQuartic_DoubleRoots)
242{
243 g_random_set_seed(123456789);
244 double const eps = 4e-5;
245 std::array<Coord, 4> roots;
246
247 for (size_t _ = 0; _ < 1000; ++_) {
248 // Generate random sorted roots, including a double root
249 for (size_t i = 0; i < 3; ++i) {
250 roots[i] = g_random_double_range(-100, 100);
251 }
252 roots[3] = roots[g_random_int_range(0, 3)];
253
254 // Generate random leading coefficient
255 Coord a = 0;
256 while (a == 0) {
257 a = g_random_double_range(-100, 100);
258 }
259
260 // Generate symmetric basis polynomials in roots
261 Coord const sym1 = roots[0] + roots[1] + roots[2] + roots[3];
262 Coord const sym2 = roots[0] * roots[1] +
263 roots[0] * roots[2] +
264 roots[0] * roots[3] +
265 roots[1] * roots[2] +
266 roots[1] * roots[3] +
267 roots[2] * roots[3];
268 Coord const sym3 = roots[0] * roots[1] * roots[2] +
269 roots[0] * roots[1] * roots[3] +
270 roots[0] * roots[2] * roots[3] +
271 roots[1] * roots[2] * roots[3];
272 Coord const sym4 = roots[0] * roots[1] * roots[2] * roots[3];
273
274 // Try to recover roots from the coefficients of the polynomial
275 auto const recovered = solve_quartic(a, -a * sym1, a * sym2, -a * sym3, a * sym4);
276 for (Coord found : recovered) {
277 double best_relative_error = infinity();
278 for (Coord root : roots) {
279 best_relative_error = std::min(best_relative_error, std::abs((found - root) / root));
280 }
281 EXPECT_LE(best_relative_error, eps);
282 }
283 }
284}
285
287TEST(PolynomialTest, SolveQuartic_0_roots)
288{
289 g_random_set_seed(0xA380B737);
290 for (size_t _ = 0; _ < 10'000; ++_) {
291 // Create two irreducible quadratics
292 auto const &[a1, b1, c1] = get_random_irreducible_quadratic();
293 auto const &[a2, b2, c2] = get_random_irreducible_quadratic();
294
295 // Try to recover roots from the product of those two quadratics
296 auto const recovered = solve_quartic(a1 * a2,
297 a1 * b2 + b1 * a2,
298 a1 * c2 + b1 * b2 + c1 * a2,
299 b1 * c2 + c1 * b2,
300 c1 * c2);
301 EXPECT_TRUE(recovered.empty());
302 }
303}
304
306TEST(PolynomialTest, SolveQuartic_degenerate)
307{
308 g_random_set_seed(0xB737A380);
309 for (size_t _ = 0; _ < 10'000; ++_) {
310 auto const b = g_random_double_range(-100, 100);
311 auto const c = g_random_double_range(-100, 100);
312 auto const d = g_random_double_range(-100, 100);
313 auto const e = g_random_double_range(-100, 100);
314
315 // Check the leading coefficient being 0
316 auto const degen1 = solve_quartic(0, b, c, d, e);
317 auto const as_cubic = solve_cubic(b, c, d, e);
318 EXPECT_EQ(degen1, as_cubic);
319
320 // Check first 2 coefficients being zero
321 auto const degen2 = solve_quartic(0, 0, c, d, e);
322 auto const as_quadratic = solve_quadratic(c, d, e);
323 EXPECT_EQ(degen2, as_quadratic);
324
325 double a = 0;
326 while (std::abs(a) < 1e-3) {
327 a = g_random_double_range(-100, 100);
328 }
329
330 // Check the case of a cubic polynomial multiplied by x
331 auto const degen3 = solve_quartic(a, b, c, d, 0);
332 auto cubic_and_zero = solve_cubic(a, b, c, d);
333 cubic_and_zero.push_back(0);
334 std::sort(cubic_and_zero.begin(), cubic_and_zero.end());
335 EXPECT_EQ(degen3, cubic_and_zero);
336
337 // Check the case of a quadratic polynomial multiplied by x^2
338 auto const degen4 = solve_quartic(a, b, c, 0, 0);
339 auto quad_and_zero = solve_quadratic(a, b, c);
340 quad_and_zero.push_back(0);
341 quad_and_zero.push_back(0);
342 std::sort(quad_and_zero.begin(), quad_and_zero.end());
343 EXPECT_EQ(degen4, quad_and_zero);
344 }
345}
Polynomial in canonical (monomial) basis.
Definition polynomial.h:50
RootCluster root
Css & result
double c[8][4]
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
Various utility functions.
Definition affine.h:22
std::vector< Coord > solve_quartic(Coord a, Coord b, Coord c, Coord d, Coord e)
Analytically solve quartic equation.
static float sign(double number)
Returns +1 for positive numbers, -1 for negative numbers, and 0 otherwise.
std::vector< Coord > solve_quadratic(Coord a, Coord b, Coord c)
Analytically solve quadratic equation.
std::vector< Coord > solve_cubic(Coord a, Coord b, Coord c, Coord d)
Analytically solve cubic equation.
std::vector< double > roots(SBasis const &s)
TEST(AffineTest, Equality)
T sqr(const T &x)
Definition math-utils.h:57
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
static std::array< Coord, 3 > get_random_irreducible_quadratic()
Return the coefficients of a random irreducible quadratic polynomial.
Polynomial in canonical (monomial) basis.