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 { 21 struct GA_Options 22 { 24 double min_range = -100; 26 double max_range = 100; 28 unsigned int num_of_generations = 10; 30 unsigned int sample_size = 1000; 32 unsigned int data_size = 100000; 34 double mutation_percentage = 0.01; 35 }; 36 37 namespace detail 38 { 39 template<typename T> 40 [[nodiscard("MATH::ABS(T) returns a value of type T")]] T ABS(const T& n) noexcept 41 { 42 return n < 0 ? n * -1 : n; 43 } 44 45 template<typename T> 46 [[nodiscard("MATH::NEGATE(T) returns a value of type T")]] T NEGATE(const T& n) noexcept 47 { 48 return n * -1; 49 } 50 51 template<typename T> 52 [[nodiscard("MATH::POW(T, int) returns a value of type T")]] T POW(const T& n, const int& exp) noexcept 53 { 54 if (exp == 0) 55 return 1; 56 57 T res = n; 58 for (int i = 1; i < exp; i++) 59 { 60 res *= n; 61 } 62 63 return res; 64 } 65 66 template<typename T> 67 [[nodiscard("MATH::SUM(std::vector<T>) returns a value of type T")]] T SUM(const std::vector<T>& vec) noexcept 68 { 69 T res{}; 70 for (auto& val : vec) 71 res += val; 72 return res; 73 } 74 75 template<typename T> 76 [[nodiscard]] T MEDIAN(std::vector<T> vec) noexcept 77 { 78 std::sort( 79 vec.begin(), 80 vec.end(), 81 [](const auto& lhs, const auto& rhs) { 82 return lhs < rhs; 83 }); 84 85 return vec[vec.size() / 2]; 86 } 87 88 template<typename T> 89 [[nodiscard]] double MEAN(const std::vector<T>& vec) noexcept 90 { 91 return SUM(vec) / vec.size(); 92 } 93 94 template<typename T> 95 [[noreturn]] void SortASC(std::vector<T>& vec) 96 { 97 std::sort( 98 std::execution::par, 99 vec.begin(), vec.end(), 100 [](const auto& lhs, const auto& rhs) { 101 return lhs < rhs; 102 }); 103 } 104 105 template<typename T> 106 [[noreturn]] void SortDESC(std::vector<T>& vec) 107 { 108 std::sort( 109 std::execution::par, 110 vec.begin(), vec.end(), 111 [](const auto& lhs, const auto& rhs) { 112 return lhs > rhs; 113 }); 114 } 115 116 template <int lrgst_expo> // Genetic Algorithm helper struct 117 struct GA_Solution 118 { 119 double rank, x, y_val; 120 bool ranked; 121 122 GA_Solution() : rank(0), x(0), y_val(0), ranked(false) {} 123 GA_Solution(double Rank, double x_val, double y = 0) : rank(Rank), x(x_val), y_val(y), ranked(false) {} 124 virtual ~GA_Solution() = default; 125 126 void fitness(const std::vector<int>& constants) 127 { 128 double ans = 0; 129 for (int i = lrgst_expo; i >= 0; i--) 130 ans += constants[i] * POW(x, (lrgst_expo - i)); 131 132 ans -= y_val; 133 rank = (ans == 0) ? DBL_MAX : ABS(1 / ans); 134 } 135 }; 136 } 137 138 using namespace detail; 143 template <int lrgst_expo> 144 class Function 145 { 146 private: 147 std::vector<int> constants; 148 149 public: 150 // Speicialty function to get the real roots of a Quadratic Function without relying on a Genetic Algorithm to approximate 151 friend std::vector<double> QuadraticSolve(const Function<2>& f); 152 153 public: 158 Function(const std::vector<int>& constnts); 163 Function(std::vector<int>&& constnts); 164 Function(const Function& other) = default; 165 Function(Function&& other) noexcept = default; 166 virtual ~Function(); 167 168 Function& operator=(const Function& other) = default; 169 Function& operator=(Function&& other) noexcept = default; 170 171 // Operator function to display function object in a human readable format 172 friend std::ostream& operator<<(std::ostream& os, const Function<lrgst_expo> func) 173 { 174 if (lrgst_expo == 0) 175 { 176 os << func.constants[0]; 177 return os; 178 } 179 180 if (func.constants[0] == 1) 181 os << "x"; 182 else if (func.constants[0] == -1) 183 os << "-x"; 184 else 185 os << func.constants[0] << "x"; 186 187 if (lrgst_expo != 1) 188 os << "^" << lrgst_expo; 189 190 for (int i = lrgst_expo - 1; i > 0; i--) 191 { 192 int n = func.constants[lrgst_expo - i]; 193 if (n == 0) continue; 194 195 auto s = n > 0 ? " + " : " - "; 196 197 if (n != 1) 198 os << s << ABS(n) << "x"; 199 else 200 os << s << "x"; 201 202 if (i != 1) 203 os << "^" << i; 204 } 205 206 int n = func.constants[lrgst_expo]; 207 if (n == 0) return os; 208 209 auto s = n > 0 ? " + " : " - "; 210 os << s; 211 212 os << ABS(n); 213 214 return os; 215 } 216 217 template<int e1, int e2, int r> 218 friend Function<r> operator+(const Function<e1>& f1, const Function<e2>& f2); // Operator to add two functions 219 template<int e1, int e2, int r> 220 friend Function<r> operator-(const Function<e1>& f1, const Function<e2>& f2); // Operator to subtract two functions 221 222 // Operators to multiply a function by a constant (Scaling it) 223 friend Function<lrgst_expo> operator*(const Function<lrgst_expo>& f, const int& c) 224 { 225 if (c == 1) return f; 226 if (c == 0) throw std::logic_error("Cannot multiply a function by 0"); 227 228 std::vector<int> res; 229 for (auto& val : f.constants) 230 res.push_back(c * val); 231 232 return Function<lrgst_expo>(res); 233 } 234 Function<lrgst_expo>& operator*=(const int& c) 235 { 236 if (c == 1) return *this; 237 if (c == 0) throw std::logic_error("Cannot multiply a function by 0"); 238 239 for (auto& val : this->constants) 240 val *= c; 241 242 return *this; 243 } 244 249 [[nodiscard("MATH::EXP::Function::differential() returns the differential, the calling object is not changed")]] 250 Function<lrgst_expo - 1> differential() const; 251 257 [[nodiscard]] std::vector<double> get_real_roots(const GA_Options& options = GA_Options()) const; 258 264 [[nodiscard]] double solve_y(const double& x_val) const noexcept; 265 272 [[nodiscard]] std::vector<double> solve_x(const double& y_val, const GA_Options& options = GA_Options()) const; 273 }; 274 280 std::vector<double> QuadraticSolve(const Function<2>& f) 281 { 282 std::vector<double> res; 283 284 const int& a = f.constants[0]; 285 const int& b = f.constants[1]; 286 const int& c = f.constants[2]; 287 288 const double sqr_val = static_cast<double>(POW(b, 2) - (4 * a * c)); 289 290 if (sqr_val < 0) 291 { 292 return res; 293 } 294 295 res.push_back(((NEGATE(b) + sqrt(sqr_val)) / 2 * a)); 296 res.push_back(((NEGATE(b) - sqrt(sqr_val)) / 2 * a)); 297 return res; 298 } 299 300 template<int e1, int e2, int r = (e1 > e2 ? e1 : e2)> 301 Function<r> operator+(const Function<e1>& f1, const Function<e2>& f2) 302 { 303 std::vector<int> res; 304 if (e1 > e2) 305 { 306 for (auto& val : f1.constants) 307 res.push_back(val); 308 309 int i = e1 - e2; 310 for (auto& val : f2.constants) 311 { 312 res[i] += val; 313 i++; 314 } 315 } 316 else 317 { 318 for (auto& val : f2.constants) 319 res.push_back(val); 320 321 int i = e2 - e1; 322 for (auto& val : f1.constants) 323 { 324 res[i] += val; 325 i++; 326 } 327 } 328 329 return Function<r>{res}; 330 } 331 332 template<int e1, int e2, int r = (e1 > e2 ? e1 : e2)> 333 Function<r> operator-(const Function<e1>& f1, const Function<e2>& f2) 334 { 335 std::vector<int> res; 336 if (e1 > e2) 337 { 338 for (auto& val : f1.constants) 339 res.push_back(val); 340 341 int i = e1 - e2; 342 for (auto& val : f2.constants) 343 { 344 res[i] -= val; 345 i++; 346 } 347 } 348 else 349 { 350 for (auto& val : f2.constants) 351 res.push_back(val); 352 353 int i = e2 - e1; 354 355 for (int j = 0; j < i; j++) 356 res[j] *= -1; 357 358 for (auto& val : f1.constants) 359 { 360 res[i] = val - res[i]; 361 i++; 362 } 363 } 364 365 return Function<r>{res}; 366 } 367 368 template <int lrgst_expo> 369 Function<lrgst_expo>::Function(const std::vector<int>& constnts) 370 { 371 if (lrgst_expo < 0) 372 throw std::logic_error("Function template argument must not be less than 0"); 373 374 if (constnts.size() != lrgst_expo + 1) 375 throw std::logic_error("Function<n> must be created with (n+1) integers in vector object"); 376 377 if (constnts[0] == 0) 378 throw std::logic_error("First value should not be 0"); 379 380 constants = constnts; 381 } 382 383 template<int lrgst_expo> 384 Function<lrgst_expo>::Function(std::vector<int>&& constnts) 385 { 386 if (lrgst_expo < 0) 387 throw std::logic_error("Function template argument must not be less than 0"); 388 389 if (constnts.size() != lrgst_expo + 1) 390 throw std::logic_error("Function<n> must be created with (n+1) integers in vector object"); 391 392 if (constnts[0] == 0) 393 throw std::logic_error("First value should not be 0"); 394 395 constants = std::move(constnts); 396 } 397 398 template <int lrgst_expo> 399 Function<lrgst_expo>::~Function() 400 { 401 constants.clear(); 402 } 403 404 template <int lrgst_expo> 405 Function<lrgst_expo - 1> Function<lrgst_expo>::differential() const 406 { 407 if (lrgst_expo == 0) 408 throw std::logic_error("Cannot differentiate a number (Function<0>)"); 409 410 std::vector<int> result; 411 for (int i = 0; i < lrgst_expo; i++) 412 { 413 result.push_back(constants[i] * (lrgst_expo - i)); 414 } 415 416 return Function<lrgst_expo - 1>{result}; 417 } 418 419 template<int lrgst_expo> 420 std::vector<double> Function<lrgst_expo>::get_real_roots(const GA_Options& options) const 421 { 422 // Create initial random solutions 423 std::random_device device; 424 std::uniform_real_distribution<double> unif(options.min_range, options.max_range); 425 std::vector<GA_Solution<lrgst_expo>> solutions; 426 427 solutions.resize(options.data_size); 428 for (unsigned int i = 0; i < options.sample_size; i++) 429 solutions[i] = (GA_Solution<lrgst_expo>{0, unif(device)}); 430 431 float timer{ 0 }; 432 433 for (unsigned int count = 0; count < options.num_of_generations; count++) 434 { 435 std::generate(std::execution::par, solutions.begin() + options.sample_size, solutions.end(), [&unif, &device]() { 436 return GA_Solution<lrgst_expo>{0, unif(device)}; 437 }); 438 439 // Run our fitness function 440 for (auto& s : solutions) { s.fitness(constants); } 441 442 // Sort our solutions by rank 443 std::sort(std::execution::par, solutions.begin(), solutions.end(), 444 [](const auto& lhs, const auto& rhs) { 445 return lhs.rank > rhs.rank; 446 }); 447 448 // Take top solutions 449 std::vector<GA_Solution<lrgst_expo>> sample; 450 std::copy( 451 solutions.begin(), 452 solutions.begin() + options.sample_size, 453 std::back_inserter(sample) 454 ); 455 solutions.clear(); 456 457 if (count + 1 == options.num_of_generations) 458 { 459 std::copy( 460 sample.begin(), 461 sample.end(), 462 std::back_inserter(solutions) 463 ); 464 sample.clear(); 465 break; 466 } 467 468 // Mutate the top solutions by % 469 std::uniform_real_distribution<double> m((1 - options.mutation_percentage), (1 + options.mutation_percentage)); 470 std::for_each(sample.begin(), sample.end(), [&m, &device](auto& s) { 471 s.x *= m(device); 472 }); 473 474 // Cross over not needed as it's only one value 475 476 std::copy( 477 sample.begin(), 478 sample.end(), 479 std::back_inserter(solutions) 480 ); 481 sample.clear(); 482 solutions.resize(options.data_size); 483 } 484 485 std::sort(solutions.begin(), solutions.end(), 486 [](const auto& lhs, const auto& rhs) { 487 return lhs.x < rhs.x; 488 }); 489 490 std::vector<double> ans; 491 for (auto& s : solutions) 492 { 493 ans.push_back(s.x); 494 } 495 return ans; 496 } 497 498 template<int lrgst_expo> 499 double Function<lrgst_expo>::solve_y(const double& x_val) const noexcept 500 { 501 std::vector<bool> exceptions; 502 503 for (int i : constants) 504 exceptions.push_back(i != 0); 505 506 double ans{ 0 }; 507 for (int i = lrgst_expo; i >= 0; i--) 508 { 509 if (exceptions[i]) 510 ans += constants[i] * POW(x_val, (lrgst_expo - i)); 511 } 512 513 return ans; 514 } 515 516 template<int lrgst_expo> 517 inline std::vector<double> Function<lrgst_expo>::solve_x(const double& y_val, const GA_Options& options) const 518 { 519 // Create initial random solutions 520 std::random_device device; 521 std::uniform_real_distribution<double> unif(options.min_range, options.max_range); 522 std::vector<GA_Solution<lrgst_expo>> solutions; 523 524 solutions.resize(options.data_size); 525 for (unsigned int i = 0; i < options.sample_size; i++) 526 solutions[i] = (GA_Solution<lrgst_expo>{0, unif(device), y_val}); 527 528 for (unsigned int count = 0; count < options.num_of_generations; count++) 529 { 530 std::generate(std::execution::par, solutions.begin() + options.sample_size, solutions.end(), [&unif, &device, &y_val]() { 531 return GA_Solution<lrgst_expo>{0, unif(device), y_val}; 532 }); 533 534 535 // Run our fitness function 536 for (auto& s : solutions) { s.fitness(constants); } 537 538 // Sort our solutions by rank 539 std::sort(std::execution::par, solutions.begin(), solutions.end(), 540 [](const auto& lhs, const auto& rhs) { 541 return lhs.rank > rhs.rank; 542 }); 543 544 // Take top solutions 545 std::vector<GA_Solution<lrgst_expo>> sample; 546 std::copy( 547 solutions.begin(), 548 solutions.begin() + options.sample_size, 549 std::back_inserter(sample) 550 ); 551 solutions.clear(); 552 553 if (count + 1 == options.num_of_generations) 554 { 555 std::copy( 556 sample.begin(), 557 sample.end(), 558 std::back_inserter(solutions) 559 ); 560 sample.clear(); 561 break; 562 } 563 564 // Mutate the top solutions by % 565 std::uniform_real_distribution<double> m((1 - options.mutation_percentage), (1 + options.mutation_percentage)); 566 std::for_each(sample.begin(), sample.end(), [&m, &device](auto& s) { 567 s.x *= m(device); 568 }); 569 570 // Cross over not needed as it's only one value 571 572 std::copy( 573 sample.begin(), 574 sample.end(), 575 std::back_inserter(solutions) 576 ); 577 sample.clear(); 578 solutions.resize(options.data_size); 579 } 580 581 std::sort(solutions.begin(), solutions.end(), 582 [](const auto& lhs, const auto& rhs) { 583 return lhs.x < rhs.x; 584 }); 585 586 std::vector<double> ans; 587 for (auto& s : solutions) 588 { 589 ans.push_back(s.x); 590 } 591 return ans; 592 } 593 } 594 } 595 596 #endif // !JONATHAN_RAMPERSAD_EXPONENTIAL_H_