Exponential Functions
Loading...
Searching...
No Matches
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
13namespace JRAMPERSAD
14{
15 namespace EXPONENTIAL
16 {
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;
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
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
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
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, ...) \
715func.SetConstants(__VA_ARGS__)
716
717#endif // !JONATHAN_RAMPERSAD_EXPONENTIAL_H_
class representing an Exponential Function (e.g 2x^2 + 4x - 1)
Definition Exponential.h:142
Function & operator*=(const int64_t &c)
Definition Exponential.h:474
Function(const Function &other)=default
Copy Constructor.
Function(Function &&other) noexcept=default
Move Constructor.
std::vector< double > get_real_roots(const GA_Options &options=GA_Options()) const
Uses a genetic algorithm to find the approximate roots of the function.
Definition Exponential.h:520
double solve_y(const double &x_val) const
Solves for y when x = user value.
Definition Exponential.h:607
Function & operator=(const Function &other)=default
Copy Assignment operator.
friend Function operator*(const Function &f, const int64_t &c)
Definition Exponential.h:449
friend Function operator-(const Function &f1, const Function &f2)
Definition Exponential.h:397
Function(const unsigned short &Lrgst_expo)
Constructor for Function class.
Definition Exponential.h:160
auto GetWhatIsTheLargestExponent() const
Definition Exponential.h:226
friend std::ostream & operator<<(std::ostream &os, const Function func)
Definition Exponential.h:294
friend std::vector< double > QuadraticSolve(const Function &f)
Uses the quadratic function to solve the roots of an entered quadratic equation.
Definition Exponential.h:234
void SetConstants(const std::vector< int64_t > &constnts)
Sets the constants of the function.
Definition Exponential.h:269
friend Function operator+(const Function &f1, const Function &f2)
Definition Exponential.h:349
std::vector< double > solve_x(const double &y_val, const GA_Options &options=GA_Options()) const
Uses a genetic algorithm to find the values of x where y = user value.
Definition Exponential.h:626
Function differential() const
Calculates the differential (dy/dx) of the Function.
Definition Exponential.h:494
Function & operator=(Function &&other) noexcept=default
Move Assignment operator.
virtual ~Function()
Destructor.
Definition Exponential.h:264
Structure for options to be used when running one of the two genetic algorithms in a Function object.
Definition Exponential.h:21
double min_range
Minimum value you believe the answer can be.
Definition Exponential.h:23
unsigned int num_of_generations
Number of times you'd like to run the algorithm (increasing this value causes the algorithm to take l...
Definition Exponential.h:27
unsigned int data_size
Amount of solutions you'd like the algorithm to generate (increasing this value causes the algorithm ...
Definition Exponential.h:31
double mutation_percentage
How much you'd like the algorithm to mutate solutions (Leave this as default in most cases)
Definition Exponential.h:33
double max_range
Maximum value you believe the answer can be.
Definition Exponential.h:25
unsigned int sample_size
Amount of approximate solutions you'd like to be returned.
Definition Exponential.h:29