sokobo
Loading...
Searching...
No Matches
numerical_methods.cpp
1#include <algorithm>
2#include <cmath>
3#include <numeric>
4#include <random>
5#include <stdexcept>
6
7#include "include/numerical_methods.h"
8#include "include/numerical_methods.h"
9#ifndef M_PI
10# define M_PI 3.14159265358979323846
11#endif
12
13#ifndef M_E
14# define M_E 2.71828
15#endif
16
17
18// Root Finding Methods
19
20double NumericalMethods::bisectionMethod(std::function<double(double)> f,
21 double a,
22 double b,
23 double tolerance)
24{
25 if (f(a) * f(b) >= 0) {
26 throw std::invalid_argument(
27 "Function must have opposite signs at endpoints for bisection method");
28 }
29
30 const int maxIterations = 1000;
31 int iterations = 0;
32
33 while (std::abs(b - a) > tolerance && iterations < maxIterations) {
34 double c = (a + b) / 2.0;
35 double fc = f(c);
36
37 if (std::abs(fc) < tolerance) {
38 return c;
39 }
40
41 if (f(a) * fc < 0) {
42 b = c;
43 } else {
44 a = c;
45 }
46
47 iterations++;
48 }
49
50 if (iterations >= maxIterations) {
51 throw std::runtime_error("Bisection method failed to converge");
52 }
53
54 return (a + b) / 2.0;
55}
56
57double NumericalMethods::newtonRaphson(std::function<double(double)> f,
58 std::function<double(double)> df,
59 double x0,
60 double tolerance)
61{
62 const int maxIterations = 1000;
63 double x = x0;
64
65 for (int i = 0; i < maxIterations; ++i) {
66 double fx = f(x);
67 double dfx = df(x);
68
69 if (std::abs(fx) < tolerance) {
70 return x;
71 }
72
73 if (std::abs(dfx) < 1e-15) {
74 throw std::runtime_error("Newton-Raphson method: derivative is zero");
75 }
76
77 double x_new = x - fx / dfx;
78
79 if (std::abs(x_new - x) < tolerance) {
80 return x_new;
81 }
82
83 x = x_new;
84 }
85
86 throw std::runtime_error("Newton-Raphson method failed to converge");
87}
88
89double NumericalMethods::secantMethod(std::function<double(double)> f,
90 double x0,
91 double x1,
92 double tolerance)
93{
94 const int maxIterations = 1000;
95
96 for (int i = 0; i < maxIterations; ++i) {
97 double fx0 = f(x0);
98 double fx1 = f(x1);
99
100 if (std::abs(fx1) < tolerance) {
101 return x1;
102 }
103
104 if (std::abs(fx1 - fx0) < 1e-15) {
105 throw std::runtime_error("Secant method: function values are too close");
106 }
107
108 double x2 = x1 - fx1 * (x1 - x0) / (fx1 - fx0);
109
110 if (std::abs(x2 - x1) < tolerance) {
111 return x2;
112 }
113
114 x0 = x1;
115 x1 = x2;
116 }
117
118 throw std::runtime_error("Secant method failed to converge");
119}
120
121std::vector<double> NumericalMethods::polynomialRoots(const Polynomial& p)
122{
123 std::vector<double> roots;
124 int degree = p.degree();
125
126 if (degree <= 0) {
127 return roots;
128 }
129
130 if (degree == 1) {
131 // Linear: ax + b = 0 => x = -b/a
132 double a = p.getCoeff(1);
133 double b = p.getCoeff(0);
134 if (std::abs(a) > 1e-15) {
135 roots.push_back(-b / a);
136 }
137 return roots;
138 }
139
140 if (degree == 2) {
141 // Quadratic: ax² + bx + c = 0
142 double a = p.getCoeff(2);
143 double b = p.getCoeff(1);
144 double c = p.getCoeff(0);
145
146 if (std::abs(a) < 1e-15) {
147 return polynomialRoots(Polynomial({c, b}));
148 }
149
150 double discriminant = b * b - 4 * a * c;
151 if (discriminant >= 0) {
152 double sqrtDisc = std::sqrt(discriminant);
153 roots.push_back((-b + sqrtDisc) / (2 * a));
154 roots.push_back((-b - sqrtDisc) / (2 * a));
155 }
156 return roots;
157 }
158
159 // For higher degree polynomials, use numerical methods
160 // Find initial bounds
161 double maxCoeff = 0;
162 for (int i = 0; i <= degree; ++i) {
163 maxCoeff = std::max(maxCoeff, std::abs(p.getCoeff(i)));
164 }
165 double bound = 1 + maxCoeff / std::abs(p.getCoeff(degree));
166
167 // Use multiple starting points to find roots
168 const int numTrials = 20;
169 std::random_device rd;
170 std::mt19937 gen(rd());
171 std::uniform_real_distribution<> dis(-bound, bound);
172
173 auto f = [&p](double x) { return p.evaluate(x); };
174 auto df = [&p](double x) { return p.derivative().evaluate(x); };
175
176 for (int trial = 0; trial < numTrials; ++trial) {
177 double x0 = dis(gen);
178 try {
179 double root = NumericalMethods::newtonRaphson(f, df, x0, 1e-10);
180
181 // Check if this is a new root
182 bool isNew = true;
183 for (double existingRoot : roots) {
184 if (std::abs(root - existingRoot) < 1e-8) {
185 isNew = false;
186 break;
187 }
188 }
189
190 if (isNew && std::abs(f(root)) < 1e-8) {
191 roots.push_back(root);
192 }
193 } catch (...) {
194 // Try different starting point
195 continue;
196 }
197 }
198
199 std::sort(roots.begin(), roots.end());
200 return roots;
201}
202
203// Linear System Solvers
204
205std::vector<double> NumericalMethods::gaussianElimination(Matrix<double> A,
206 std::vector<double> b)
207{
208 int n = A.getRows();
209 if (n != A.getCols() || n != b.size()) {
210 throw std::invalid_argument(
211 "Matrix dimensions incompatible for Gaussian elimination");
212 }
213
214 // Forward elimination with partial pivoting
215 for (int i = 0; i < n - 1; ++i) {
216 // Find pivot
217 int maxRow = i;
218 for (int k = i + 1; k < n; ++k) {
219 if (std::abs(A(k, i)) > std::abs(A(maxRow, i))) {
220 maxRow = k;
221 }
222 }
223
224 // Swap rows
225 if (maxRow != i) {
226 for (int j = 0; j < n; ++j) {
227 std::swap(A(i, j), A(maxRow, j));
228 }
229 std::swap(b[i], b[maxRow]);
230 }
231
232 // Check for singular matrix
233 if (std::abs(A(i, i)) < 1e-12) {
234 throw std::runtime_error("Matrix is singular");
235 }
236
237 // Eliminate
238 for (int k = i + 1; k < n; ++k) {
239 double factor = A(k, i) / A(i, i);
240 for (int j = i; j < n; ++j) {
241 A(k, j) -= factor * A(i, j);
242 }
243 b[k] -= factor * b[i];
244 }
245 }
246
247 // Back substitution
248 std::vector<double> x(n);
249 for (int i = n - 1; i >= 0; --i) {
250 x[i] = b[i];
251 for (int j = i + 1; j < n; ++j) {
252 x[i] -= A(i, j) * x[j];
253 }
254 x[i] /= A(i, i);
255 }
256
257 return x;
258}
259
260std::vector<double> NumericalMethods::LUDecomposition(
261 const Matrix<double>& A, const std::vector<double>& b)
262{
263 auto [L, U] = A.LUDecomposition();
264 int n = A.getRows();
265
266 // Forward substitution: Ly = b
267 std::vector<double> y(n);
268 for (int i = 0; i < n; ++i) {
269 y[i] = b[i];
270 for (int j = 0; j < i; ++j) {
271 y[i] -= L(i, j) * y[j];
272 }
273 }
274
275 // Back substitution: Ux = y
276 std::vector<double> x(n);
277 for (int i = n - 1; i >= 0; --i) {
278 x[i] = y[i];
279 for (int j = i + 1; j < n; ++j) {
280 x[i] -= U(i, j) * x[j];
281 }
282 x[i] /= U(i, i);
283 }
284
285 return x;
286}
287
288std::vector<double> NumericalMethods::jacobiMethod(const Matrix<double>& A,
289 const std::vector<double>& b,
290 double tolerance,
291 int maxIterations)
292{
293 int n = A.getRows();
294 std::vector<double> x(n, 0.0);
295 std::vector<double> x_new(n);
296
297 for (int iter = 0; iter < maxIterations; ++iter) {
298 for (int i = 0; i < n; ++i) {
299 x_new[i] = b[i];
300 for (int j = 0; j < n; ++j) {
301 if (i != j) {
302 x_new[i] -= A(i, j) * x[j];
303 }
304 }
305 x_new[i] /= A(i, i);
306 }
307
308 // Check convergence
309 double maxDiff = 0.0;
310 for (int i = 0; i < n; ++i) {
311 maxDiff = std::max(maxDiff, std::abs(x_new[i] - x[i]));
312 }
313
314 x = x_new;
315
316 if (maxDiff < tolerance) {
317 return x;
318 }
319 }
320
321 throw std::runtime_error("Jacobi method failed to converge");
322}
323
324std::vector<double> NumericalMethods::gaussSeidelMethod(
325 const Matrix<double>& A,
326 const std::vector<double>& b,
327 double tolerance,
328 int maxIterations)
329{
330 int n = A.getRows();
331 std::vector<double> x(n, 0.0);
332
333 for (int iter = 0; iter < maxIterations; ++iter) {
334 std::vector<double> x_old = x;
335
336 for (int i = 0; i < n; ++i) {
337 x[i] = b[i];
338 for (int j = 0; j < n; ++j) {
339 if (i != j) {
340 x[i] -= A(i, j) * x[j];
341 }
342 }
343 x[i] /= A(i, i);
344 }
345
346 // Check convergence
347 double maxDiff = 0.0;
348 for (int i = 0; i < n; ++i) {
349 maxDiff = std::max(maxDiff, std::abs(x[i] - x_old[i]));
350 }
351
352 if (maxDiff < tolerance) {
353 return x;
354 }
355 }
356
357 throw std::runtime_error("Gauss-Seidel method failed to converge");
358}
359
360// Interpolation Methods
361
362Polynomial NumericalMethods::lagrangeInterpolation(const std::vector<double>& x,
363 const std::vector<double>& y)
364{
365 if (x.size() != y.size() || x.empty()) {
366 throw std::invalid_argument(
367 "x and y vectors must have the same non-zero size");
368 }
369
370 int n = x.size();
371 Polynomial result(0.0);
372
373 for (int i = 0; i < n; ++i) {
374 Polynomial Li(1.0);
375
376 for (int j = 0; j < n; ++j) {
377 if (i != j) {
378 // Li *= (X - x[j]) / (x[i] - x[j])
379 Polynomial factor({-x[j], 1.0}); // X - x[j]
380 Li = Li * factor;
381 Li = Li * (1.0 / (x[i] - x[j]));
382 }
383 }
384
385 result = result + Li * y[i];
386 }
387
388 return result;
389}
390
391Polynomial NumericalMethods::newtonInterpolation(const std::vector<double>& x,
392 const std::vector<double>& y)
393{
394 if (x.size() != y.size() || x.empty()) {
395 throw std::invalid_argument(
396 "x and y vectors must have the same non-zero size");
397 }
398
399 int n = x.size();
400
401 // Compute divided differences
402 std::vector<std::vector<double>> dividedDiff(n, std::vector<double>(n));
403
404 // Initialize first column
405 for (int i = 0; i < n; ++i) {
406 dividedDiff[i][0] = y[i];
407 }
408
409 // Compute higher order differences
410 for (int j = 1; j < n; ++j) {
411 for (int i = 0; i < n - j; ++i) {
412 dividedDiff[i][j] = (dividedDiff[i + 1][j - 1] - dividedDiff[i][j - 1])
413 / (x[i + j] - x[i]);
414 }
415 }
416
417 // Build polynomial
418 Polynomial result(dividedDiff[0][0]);
419 Polynomial product(1.0);
420
421 for (int i = 1; i < n; ++i) {
422 Polynomial factor({-x[i - 1], 1.0}); // (X - x[i-1])
423 product = product * factor;
424 result = result + product * dividedDiff[0][i];
425 }
426
427 return result;
428}
429
430double NumericalMethods::splineInterpolation(const std::vector<double>& x,
431 const std::vector<double>& y,
432 double xi)
433{
434 if (x.size() != y.size() || x.size() < 2) {
435 throw std::invalid_argument(
436 "Need at least 2 points for spline interpolation");
437 }
438
439 int n = x.size();
440
441 // Find interval containing xi
442 int i = 0;
443 for (i = 0; i < n - 1; ++i) {
444 if (xi >= x[i] && xi <= x[i + 1]) {
445 break;
446 }
447 }
448
449 if (i == n - 1) {
450 i = n - 2; // Use last interval if xi is beyond range
451 }
452
453 // Linear interpolation within interval
454 double t = (xi - x[i]) / (x[i + 1] - x[i]);
455 return y[i] * (1 - t) + y[i + 1] * t;
456}
457
458// Optimization Methods
459
460double NumericalMethods::goldenSectionSearch(std::function<double(double)> f,
461 double a,
462 double b,
463 double tolerance)
464{
465 const double phi = (1 + std::sqrt(5)) / 2; // Golden ratio
466 const double resphi = 2 - phi;
467
468 double x1 = a + resphi * (b - a);
469 double x2 = a + (1 - resphi) * (b - a);
470 double f1 = f(x1);
471 double f2 = f(x2);
472
473 while (std::abs(b - a) > tolerance) {
474 if (f1 < f2) {
475 b = x2;
476 x2 = x1;
477 f2 = f1;
478 x1 = a + resphi * (b - a);
479 f1 = f(x1);
480 } else {
481 a = x1;
482 x1 = x2;
483 f1 = f2;
484 x2 = a + (1 - resphi) * (b - a);
485 f2 = f(x2);
486 }
487 }
488
489 return (a + b) / 2;
490}
491
492std::vector<double> NumericalMethods::gradientDescent(
493 std::function<double(const std::vector<double>&)> f,
494 std::function<std::vector<double>(const std::vector<double>&)> grad,
495 std::vector<double> x0,
496 double learningRate,
497 double tolerance)
498{
499 const int maxIterations = 10000;
500 std::vector<double> x = x0;
501
502 for (int iter = 0; iter < maxIterations; ++iter) {
503 std::vector<double> g = grad(x);
504
505 // Check if gradient norm is small enough
506 double gradNorm = 0.0;
507 for (double gi : g) {
508 gradNorm += gi * gi;
509 }
510 gradNorm = std::sqrt(gradNorm);
511
512 if (gradNorm < tolerance) {
513 return x;
514 }
515
516 // Update x
517 for (size_t i = 0; i < x.size(); ++i) {
518 x[i] -= learningRate * g[i];
519 }
520 }
521
522 throw std::runtime_error("Gradient descent failed to converge");
523}
524
525std::vector<double> NumericalMethods::newtonOptimization(
526 std::function<double(const std::vector<double>&)> f,
527 std::function<std::vector<double>(const std::vector<double>&)> grad,
528 std::function<Matrix<double>(const std::vector<double>&)> hessian,
529 std::vector<double> x0,
530 double tolerance)
531{
532 const int maxIterations = 1000;
533 std::vector<double> x = x0;
534
535 for (int iter = 0; iter < maxIterations; ++iter) {
536 std::vector<double> g = grad(x);
537 Matrix<double> H = hessian(x);
538
539 // Check convergence
540 double gradNorm = 0.0;
541 for (double gi : g) {
542 gradNorm += gi * gi;
543 }
544 gradNorm = std::sqrt(gradNorm);
545
546 if (gradNorm < tolerance) {
547 return x;
548 }
549
550 // Solve H * dx = -g
551 std::vector<double> negGrad(g.size());
552 for (size_t i = 0; i < g.size(); ++i) {
553 negGrad[i] = -g[i];
554 }
555
556 try {
557 std::vector<double> dx = gaussianElimination(H, negGrad);
558
559 // Update x
560 for (size_t i = 0; i < x.size(); ++i) {
561 x[i] += dx[i];
562 }
563 } catch (...) {
564 // Fallback to gradient descent step
565 for (size_t i = 0; i < x.size(); ++i) {
566 x[i] -= 0.01 * g[i];
567 }
568 }
569 }
570
571 throw std::runtime_error("Newton optimization failed to converge");
572}
573
574// Statistical Methods
575
576double NumericalMethods::mean(const std::vector<double>& data)
577{
578 if (data.empty()) {
579 throw std::invalid_argument("Cannot compute mean of empty dataset");
580 }
581
582 return std::accumulate(data.begin(), data.end(), 0.0) / data.size();
583}
584
585double NumericalMethods::variance(const std::vector<double>& data)
586{
587 if (data.size() < 2) {
588 throw std::invalid_argument("Need at least 2 data points for variance");
589 }
590
591 double mu = mean(data);
592 double sum = 0.0;
593
594 for (double x : data) {
595 sum += (x - mu) * (x - mu);
596 }
597
598 return sum / (data.size() - 1);
599}
600
601double NumericalMethods::standardDeviation(const std::vector<double>& data)
602{
603 return std::sqrt(variance(data));
604}
605
606double NumericalMethods::correlation(const std::vector<double>& x,
607 const std::vector<double>& y)
608{
609 if (x.size() != y.size() || x.size() < 2) {
610 throw std::invalid_argument("x and y must have the same size (at least 2)");
611 }
612
613 double mx = mean(x);
614 double my = mean(y);
615
616 double numerator = 0.0;
617 double sumXX = 0.0;
618 double sumYY = 0.0;
619
620 for (size_t i = 0; i < x.size(); ++i) {
621 double dx = x[i] - mx;
622 double dy = y[i] - my;
623 numerator += dx * dy;
624 sumXX += dx * dx;
625 sumYY += dy * dy;
626 }
627
628 double denominator = std::sqrt(sumXX * sumYY);
629 if (denominator < 1e-15) {
630 throw std::runtime_error("Cannot compute correlation: zero variance");
631 }
632
633 return numerator / denominator;
634}
635
636std::pair<double, double> NumericalMethods::linearRegression(
637 const std::vector<double>& x, const std::vector<double>& y)
638{
639 if (x.size() != y.size() || x.size() < 2) {
640 throw std::invalid_argument("x and y must have the same size (at least 2)");
641 }
642
643 double mx = mean(x);
644 double my = mean(y);
645
646 double numerator = 0.0;
647 double denominator = 0.0;
648
649 for (size_t i = 0; i < x.size(); ++i) {
650 double dx = x[i] - mx;
651 numerator += dx * (y[i] - my);
652 denominator += dx * dx;
653 }
654
655 if (std::abs(denominator) < 1e-15) {
656 throw std::runtime_error(
657 "Cannot perform linear regression: zero variance in x");
658 }
659
660 double slope = numerator / denominator;
661 double intercept = my - slope * mx;
662
663 return {slope, intercept};
664}
665
666// Special Functions
667
668double NumericalMethods::gamma(double x)
669{
670 if (x <= 0) {
671 throw std::invalid_argument(
672 "Gamma function undefined for non-positive values");
673 }
674
675 // Stirling's approximation for large x
676 if (x > 10) {
677 return std::sqrt(2 * M_PI / x) * std::pow(x / M_E, x);
678 }
679
680 // Use recurrence relation: Γ(x+1) = x * Γ(x)
681 if (x > 1) {
682 return (x - 1) * gamma(x - 1);
683 }
684
685 // For 0 < x <= 1, use Lanczos approximation (simplified)
686 const double g = 7;
687 const std::vector<double> c = {0.99999999999980993,
688 676.5203681218851,
689 -1259.1392167224028,
690 771.32342877765313,
691 -176.61502916214059,
692 12.507343278686905,
693 -0.13857109526572012,
694 9.9843695780195716e-6,
695 1.5056327351493116e-7};
696
697 x -= 1;
698 double a = c[0];
699 for (size_t i = 1; i < c.size(); ++i) {
700 a += c[i] / (x + i);
701 }
702
703 double t = x + g + 0.5;
704 return std::sqrt(2 * M_PI) * std::pow(t, x + 0.5) * std::exp(-t) * a;
705}
706
707double NumericalMethods::beta(double a, double b)
708{
709 return gamma(a) * gamma(b) / gamma(a + b);
710}
711
712double NumericalMethods::erf(double x)
713{
714 // Abramowitz and Stegun approximation
715 const double a1 = 0.254829592;
716 const double a2 = -0.284496736;
717 const double a3 = 1.421413741;
718 const double a4 = -1.453152027;
719 const double a5 = 1.061405429;
720 const double p = 0.3275911;
721
722 int sign = (x >= 0) ? 1 : -1;
723 x = std::abs(x);
724
725 double t = 1.0 / (1.0 + p * x);
726 double y = 1.0
727 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * std::exp(-x * x);
728
729 return sign * y;
730}
731
732double NumericalMethods::besselJ(int n, double x)
733{
734 if (n < 0) {
735 return ((n % 2) == 0) ? besselJ(-n, x) : -besselJ(-n, x);
736 }
737
738 if (x == 0) {
739 return (n == 0) ? 1.0 : 0.0;
740 }
741
742 // For small x, use series expansion
743 if (std::abs(x) < 10) {
744 double result = 0.0;
745 double term = std::pow(x / 2, n) / gamma(n + 1);
746
747 for (int k = 0; k < 100; ++k) {
748 result += term;
749 term *= -x * x / (4 * (k + 1) * (k + n + 1));
750 if (std::abs(term) < 1e-15) {
751 break;
752 }
753 }
754
755 return result;
756 }
757
758 // For large x, use asymptotic expansion
759 double phase = x - (n + 0.5) * M_PI / 2;
760 return std::sqrt(2 / (M_PI * x)) * std::cos(phase);
761}
762
763double NumericalMethods::legendreP(int n, double x)
764{
765 if (n < 0 || std::abs(x) > 1) {
766 throw std::invalid_argument("Invalid arguments for Legendre polynomial");
767 }
768
769 if (n == 0) {
770 return 1.0;
771 }
772 if (n == 1) {
773 return x;
774 }
775
776 // Use recurrence relation: (n+1)P_{n+1}(x) = (2n+1)xP_n(x) - nP_{n-1}(x)
777 double P0 = 1.0;
778 double P1 = x;
779
780 for (int k = 2; k <= n; ++k) {
781 double P2 = ((2 * k - 1) * x * P1 - (k - 1) * P0) / k;
782 P0 = P1;
783 P1 = P2;
784 }
785
786 return P1;
787}
788
789double NumericalMethods::hermiteH(int n, double x)
790{
791 if (n < 0) {
792 throw std::invalid_argument(
793 "Hermite polynomial order must be non-negative");
794 }
795
796 if (n == 0) {
797 return 1.0;
798 }
799 if (n == 1) {
800 return 2 * x;
801 }
802
803 // Use recurrence relation: H_{n+1}(x) = 2xH_n(x) - 2nH_{n-1}(x)
804 double H0 = 1.0;
805 double H1 = 2 * x;
806
807 for (int k = 2; k <= n; ++k) {
808 double H2 = 2 * x * H1 - 2 * (k - 1) * H0;
809 H0 = H1;
810 H1 = H2;
811 }
812
813 return H1;
814}
A templated matrix class supporting various linear algebra operations.
Definition: matrix.h:23
int getCols() const
Gets the number of columns in the matrix.
Definition: matrix.h:425
std::pair< Matrix< T >, Matrix< T > > LUDecomposition() const
Performs LU decomposition of the matrix.
Definition: matrix.cpp:533
int getRows() const
Gets the number of rows in the matrix.
Definition: matrix.h:418