Exponential.h Exponential/Exponential.h 1 #pragma once 2 #ifndef JONATHAN_RAMPERSAD_EXPONENTIAL_H_ 3 #define JONATHAN_RAMPERSAD_EXPONENTIAL_H_ 4 5 #include <ostream> 6 #include <vector> 7 #include <float.h> 8 #include <random> 9 #include <algorithm> 10 #include <execution> 11 #include <exception> 12 13 namespace JRAMPERSAD 14 { 15 namespace EXPONENTIAL 16 { 20 struct GA_Options 21 { 23 double min_range = -100; 25 double max_range = 100; 27 unsigned int num_of_generations = 10; 29 unsigned int sample_size = 1000; 31 unsigned int data_size = 100000; 33 double mutation_percentage = 0.01; 34 }; 35 36 namespace detail 37 { 38 template<typename T> 39 [[nodiscard("MATH::ABS(T) returns a value of type T")]] T ABS(const T& n) noexcept 40 { 41 return n < 0 ? n * -1 : n; 42 } 43 44 template<typename T> 45 [[nodiscard("MATH::NEGATE(T) returns a value of type T")]] T NEGATE(const T& n) noexcept 46 { 47 return n * -1; 48 } 49 50 template<typename T> 51 [[nodiscard("MATH::POW(T, int) returns a value of type T")]] T POW(const T& n, const int& exp) noexcept 52 { 53 if (exp == 0) 54 return 1; 55 56 T res = n; 57 for (int i = 1; i < exp; i++) 58 { 59 res *= n; 60 } 61 62 return res; 63 } 64 65 template<typename T> 66 [[nodiscard("MATH::SUM(std::vector<T>) returns a value of type T")]] T SUM(const std::vector<T>& vec) noexcept 67 { 68 T res{}; 69 for (auto& val : vec) 70 res += val; 71 return res; 72 } 73 74 template<typename T> 75 [[nodiscard]] T MEDIAN(std::vector<T> vec) noexcept 76 { 77 std::sort( 78 vec.begin(), 79 vec.end(), 80 [](const auto& lhs, const auto& rhs) { 81 return lhs < rhs; 82 }); 83 84 return vec[vec.size() / 2]; 85 } 86 87 template<typename T> 88 [[nodiscard]] double MEAN(const std::vector<T>& vec) noexcept 89 { 90 return SUM(vec) / vec.size(); 91 } 92 93 template<typename T> 94 [[noreturn]] void SortASC(std::vector<T>& vec) 95 { 96 std::sort( 97 std::execution::par, 98 vec.begin(), vec.end(), 99 [](const auto& lhs, const auto& rhs) { 100 return lhs < rhs; 101 }); 102 } 103 104 template<typename T> 105 [[noreturn]] void SortDESC(std::vector<T>& vec) 106 { 107 std::sort( 108 std::execution::par, 109 vec.begin(), vec.end(), 110 [](const auto& lhs, const auto& rhs) { 111 return lhs > rhs; 112 }); 113 } 114 115 // Genetic Algorithm helper struct 116 struct GA_Solution 117 { 118 unsigned short lrgst_expo; 119 double rank, x, y_val; 120 121 GA_Solution() : lrgst_expo(0), rank(0), x(0), y_val(0) {} 122 GA_Solution(unsigned short Lrgst_expo, double Rank, double x_val, double y = 0) : lrgst_expo(Lrgst_expo), rank(Rank), x(x_val), y_val(y) {} 123 virtual ~GA_Solution() = default; 124 125 void fitness(const std::vector<int64_t>& constants) 126 { 127 double ans = 0; 128 for (int i = lrgst_expo; i >= 0; i--) 129 ans += constants[i] * POW(x, (lrgst_expo - i)); 130 131 ans -= y_val; 132 rank = (ans == 0) ? DBL_MAX : ABS(1 / ans); 133 } 134 }; 135 } 136 137 using namespace detail; 141 class Function 142 { 143 private: 144 const unsigned short lrgst_expo; 145 std::vector<int64_t> constants; 146 147 bool bInitialized; 148 149 void CanPerform() const { if (!bInitialized) throw std::logic_error("Function object not initialized fully! Please call .SetConstants() to initialize"); } 150 151 public: 152 // Speicialty function to get the real roots of a Quadratic Function without relying on a Genetic Algorithm to approximate 153 friend std::vector<double> QuadraticSolve(const Function& f); 154 155 public: 160 Function(const unsigned short& Lrgst_expo) : lrgst_expo(Lrgst_expo), bInitialized(false) 161 { 162 if (lrgst_expo < 0) 163 throw std::logic_error("Function template argument must not be less than 0"); 164 constants.reserve(Lrgst_expo); 165 } 167 virtual ~Function(); 169 Function(const Function& other) = default; 171 Function(Function&& other) noexcept = default; 173 Function& operator=(const Function& other) = default; 175 Function& operator=(Function&& other) noexcept = default; 176 181 void SetConstants(const std::vector<int64_t>& constnts); 186 void SetConstants(std::vector<int64_t>&& constnts); 187 188 friend std::ostream& operator<<(std::ostream& os, const Function func); 189 190 friend Function operator+(const Function& f1, const Function& f2); 191 friend Function operator-(const Function& f1, const Function& f2); 192 193 friend Function operator*(const Function& f, const int64_t& c); 194 Function& operator*=(const int64_t& c); 195 200 [[nodiscard("MATH::EXP::Function::differential() returns the differential, the calling object is not changed")]] 201 Function differential() const; 202 208 [[nodiscard]] std::vector<double> get_real_roots(const GA_Options& options = GA_Options()) const; 209 215 [[nodiscard]] double solve_y(const double& x_val) const; 216 223 [[nodiscard]] std::vector<double> solve_x(const double& y_val, const GA_Options& options = GA_Options()) const; 224 226 [[nodiscard]] auto GetWhatIsTheLargestExponent() const { return lrgst_expo; } 227 }; 228 234 std::vector<double> QuadraticSolve(const Function& f) 235 { 236 try 237 { 238 if (f.lrgst_expo != 2) throw std::logic_error("Function f is not a quadratic function"); 239 f.CanPerform(); 240 } 241 catch (const std::exception& e) 242 { 243 throw e; 244 } 245 246 std::vector<double> res; 247 248 const auto& a = f.constants[0]; 249 const auto& b = f.constants[1]; 250 const auto& c = f.constants[2]; 251 252 const double sqr_val = static_cast<double>(POW(b, 2) - (4 * a * c)); 253 254 if (sqr_val < 0) 255 { 256 return res; 257 } 258 259 res.push_back(((NEGATE(b) + sqrt(sqr_val)) / 2 * a)); 260 res.push_back(((NEGATE(b) - sqrt(sqr_val)) / 2 * a)); 261 return res; 262 } 263 264 Function::~Function() 265 { 266 constants.clear(); 267 } 268 269 void Function::SetConstants(const std::vector<int64_t>& constnts) 270 { 271 if (constnts.size() != lrgst_expo + 1) 272 throw std::logic_error("Function<n> must be created with (n+1) integers in vector object"); 273 274 if (constnts[0] == 0) 275 throw std::logic_error("First value should not be 0"); 276 277 constants = constnts; 278 bInitialized = true; 279 } 280 281 void Function::SetConstants(std::vector<int64_t>&& constnts) 282 { 283 if (constnts.size() != lrgst_expo + 1) 284 throw std::logic_error("Function<n> must be created with (n+1) integers in vector object"); 285 286 if (constnts[0] == 0) 287 throw std::logic_error("First value should not be 0"); 288 289 constants = std::move(constnts); 290 bInitialized = true; 291 } 292 294 std::ostream& operator<<(std::ostream& os, const Function func) 295 { 296 try 297 { 298 func.CanPerform(); 299 } 300 catch (const std::exception& e) 301 { 302 throw e; 303 } 304 305 if (func.lrgst_expo == 0) 306 { 307 os << func.constants[0]; 308 return os; 309 } 310 311 if (func.constants[0] == 1) 312 os << "x"; 313 else if (func.constants[0] == -1) 314 os << "-x"; 315 else 316 os << func.constants[0] << "x"; 317 318 if (func.lrgst_expo != 1) 319 os << "^" << func.lrgst_expo; 320 321 for (auto i = func.lrgst_expo - 1; i > 0; i--) 322 { 323 auto n = func.constants[func.lrgst_expo - i]; 324 if (n == 0) continue; 325 326 auto s = n > 0 ? " + " : " - "; 327 328 if (n != 1) 329 os << s << ABS(n) << "x"; 330 else 331 os << s << "x"; 332 333 if (i != 1) 334 os << "^" << i; 335 } 336 337 auto n = func.constants[func.lrgst_expo]; 338 if (n == 0) return os; 339 340 auto s = n > 0 ? " + " : " - "; 341 os << s; 342 343 os << ABS(n); 344 345 return os; 346 } 347 349 Function operator+(const Function& f1, const Function& f2) 350 { 351 try 352 { 353 f1.CanPerform(); 354 f2.CanPerform(); 355 } 356 catch (const std::exception& e) 357 { 358 throw e; 359 } 360 361 auto e1 = f1.lrgst_expo; 362 auto e2 = f2.lrgst_expo; 363 auto r = e1 > e2 ? e1 : e2; 364 365 std::vector<int64_t> res; 366 if (e1 > e2) 367 { 368 for (auto& val : f1.constants) 369 res.push_back(val); 370 371 auto i = e1 - e2; 372 for (auto& val : f2.constants) 373 { 374 res[i] += val; 375 i++; 376 } 377 } 378 else 379 { 380 for (auto& val : f2.constants) 381 res.push_back(val); 382 383 int i = e2 - e1; 384 for (auto& val : f1.constants) 385 { 386 res[i] += val; 387 i++; 388 } 389 } 390 391 Function f(r); 392 f.SetConstants(res); 393 return f; 394 } 395 397 Function operator-(const Function& f1, const Function& f2) 398 { 399 try 400 { 401 f1.CanPerform(); 402 f2.CanPerform(); 403 } 404 catch (const std::exception& e) 405 { 406 throw e; 407 } 408 409 auto e1 = f1.lrgst_expo; 410 auto e2 = f2.lrgst_expo; 411 auto r = e1 > e2 ? e1 : e2; 412 413 std::vector<int64_t> res; 414 if (e1 > e2) 415 { 416 for (auto& val : f1.constants) 417 res.push_back(val); 418 419 auto i = e1 - e2; 420 for (auto& val : f2.constants) 421 { 422 res[i] -= val; 423 i++; 424 } 425 } 426 else 427 { 428 for (auto& val : f2.constants) 429 res.push_back(val); 430 431 int i = e2 - e1; 432 433 for (int j = 0; j < i; j++) 434 res[j] *= -1; 435 436 for (auto& val : f1.constants) 437 { 438 res[i] = val - res[i]; 439 i++; 440 } 441 } 442 443 Function f(r); 444 f.SetConstants(res); 445 return f; 446 } 447 449 Function operator*(const Function& f, const int64_t& c) 450 { 451 try 452 { 453 f.CanPerform(); 454 } 455 catch (const std::exception& e) 456 { 457 throw e; 458 } 459 460 if (c == 1) return f; 461 if (c == 0) throw std::logic_error("Cannot multiply a function by 0"); 462 463 std::vector<int64_t> res; 464 for (auto& val : f.constants) 465 res.push_back(c * val); 466 467 Function f_res(f.lrgst_expo); 468 f_res.SetConstants(res); 469 470 return f_res; 471 } 472 474 Function& Function::operator*=(const int64_t& c) 475 { 476 try 477 { 478 this->CanPerform(); 479 } 480 catch (const std::exception& e) 481 { 482 throw e; 483 } 484 485 if (c == 1) return *this; 486 if (c == 0) throw std::logic_error("Cannot multiply a function by 0"); 487 488 for (auto& val : this->constants) 489 val *= c; 490 491 return *this; 492 } 493 494 Function Function::differential() const 495 { 496 try 497 { 498 this->CanPerform(); 499 } 500 catch (const std::exception& e) 501 { 502 throw e; 503 } 504 505 if (lrgst_expo == 0) 506 throw std::logic_error("Cannot differentiate a number (Function<0>)"); 507 508 std::vector<int64_t> result; 509 for (int i = 0; i < lrgst_expo; i++) 510 { 511 result.push_back(constants[i] * (lrgst_expo - i)); 512 } 513 514 Function f{ (unsigned short)(lrgst_expo - 1) }; 515 f.SetConstants(result); 516 517 return f; 518 } 519 520 std::vector<double> Function::get_real_roots(const GA_Options& options) const 521 { 522 try 523 { 524 this->CanPerform(); 525 } 526 catch (const std::exception& e) 527 { 528 throw e; 529 } 530 531 // Create initial random solutions 532 std::random_device device; 533 std::uniform_real_distribution<double> unif(options.min_range, options.max_range); 534 std::vector<GA_Solution> solutions; 535 536 solutions.resize(options.data_size); 537 for (unsigned int i = 0; i < options.sample_size; i++) 538 solutions[i] = (GA_Solution{lrgst_expo, 0, unif(device)}); 539 540 float timer{ 0 }; 541 542 for (unsigned int count = 0; count < options.num_of_generations; count++) 543 { 544 std::generate(std::execution::par, solutions.begin() + options.sample_size, solutions.end(), [this, &unif, &device]() { 545 return GA_Solution{lrgst_expo, 0, unif(device)}; 546 }); 547 548 // Run our fitness function 549 for (auto& s : solutions) { s.fitness(constants); } 550 551 // Sort our solutions by rank 552 std::sort(std::execution::par, solutions.begin(), solutions.end(), 553 [](const auto& lhs, const auto& rhs) { 554 return lhs.rank > rhs.rank; 555 }); 556 557 // Take top solutions 558 std::vector<GA_Solution> sample; 559 std::copy( 560 solutions.begin(), 561 solutions.begin() + options.sample_size, 562 std::back_inserter(sample) 563 ); 564 solutions.clear(); 565 566 if (count + 1 == options.num_of_generations) 567 { 568 std::copy( 569 sample.begin(), 570 sample.end(), 571 std::back_inserter(solutions) 572 ); 573 sample.clear(); 574 break; 575 } 576 577 // Mutate the top solutions by % 578 std::uniform_real_distribution<double> m((1 - options.mutation_percentage), (1 + options.mutation_percentage)); 579 std::for_each(sample.begin(), sample.end(), [&m, &device](auto& s) { 580 s.x *= m(device); 581 }); 582 583 // Cross over not needed as it's only one value 584 585 std::copy( 586 sample.begin(), 587 sample.end(), 588 std::back_inserter(solutions) 589 ); 590 sample.clear(); 591 solutions.resize(options.data_size); 592 } 593 594 std::sort(solutions.begin(), solutions.end(), 595 [](const auto& lhs, const auto& rhs) { 596 return lhs.x < rhs.x; 597 }); 598 599 std::vector<double> ans; 600 for (auto& s : solutions) 601 { 602 ans.push_back(s.x); 603 } 604 return ans; 605 } 606 607 double Function::solve_y(const double& x_val) const 608 { 609 try 610 { 611 this->CanPerform(); 612 } 613 catch (const std::exception& e) 614 { 615 throw e; 616 } 617 618 double ans{ 0 }; 619 for (int i = lrgst_expo; i >= 0; i--) 620 { 621 ans += constants[i] * POW(x_val, (lrgst_expo - i)); 622 } 623 return ans; 624 } 625 626 inline std::vector<double> Function::solve_x(const double& y_val, const GA_Options& options) const 627 { 628 try 629 { 630 this->CanPerform(); 631 } 632 catch (const std::exception& e) 633 { 634 throw e; 635 } 636 637 // Create initial random solutions 638 std::random_device device; 639 std::uniform_real_distribution<double> unif(options.min_range, options.max_range); 640 std::vector<GA_Solution> solutions; 641 642 solutions.resize(options.data_size); 643 for (unsigned int i = 0; i < options.sample_size; i++) 644 solutions[i] = (GA_Solution{lrgst_expo, 0, unif(device), y_val}); 645 646 for (unsigned int count = 0; count < options.num_of_generations; count++) 647 { 648 std::generate(std::execution::par, solutions.begin() + options.sample_size, solutions.end(), [this, &unif, &device, &y_val]() { 649 return GA_Solution{lrgst_expo, 0, unif(device), y_val}; 650 }); 651 652 653 // Run our fitness function 654 for (auto& s : solutions) { s.fitness(constants); } 655 656 // Sort our solutions by rank 657 std::sort(std::execution::par, solutions.begin(), solutions.end(), 658 [](const auto& lhs, const auto& rhs) { 659 return lhs.rank > rhs.rank; 660 }); 661 662 // Take top solutions 663 std::vector<GA_Solution> sample; 664 std::copy( 665 solutions.begin(), 666 solutions.begin() + options.sample_size, 667 std::back_inserter(sample) 668 ); 669 solutions.clear(); 670 671 if (count + 1 == options.num_of_generations) 672 { 673 std::copy( 674 sample.begin(), 675 sample.end(), 676 std::back_inserter(solutions) 677 ); 678 sample.clear(); 679 break; 680 } 681 682 // Mutate the top solutions by % 683 std::uniform_real_distribution<double> m((1 - options.mutation_percentage), (1 + options.mutation_percentage)); 684 std::for_each(sample.begin(), sample.end(), [&m, &device](auto& s) { 685 s.x *= m(device); 686 }); 687 688 // Cross over not needed as it's only one value 689 690 std::copy( 691 sample.begin(), 692 sample.end(), 693 std::back_inserter(solutions) 694 ); 695 sample.clear(); 696 solutions.resize(options.data_size); 697 } 698 699 std::sort(solutions.begin(), solutions.end(), 700 [](const auto& lhs, const auto& rhs) { 701 return lhs.x < rhs.x; 702 }); 703 704 std::vector<double> ans; 705 for (auto& s : solutions) 706 { 707 ans.push_back(s.x); 708 } 709 return ans; 710 } 711 } 712 } 713 714 #define INITIALIZE_EXPO_FUNCTION(func, ...) \ 715 func.SetConstants(__VA_ARGS__) 716 717 #endif // !JONATHAN_RAMPERSAD_EXPONENTIAL_H_