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 {
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>
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
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)
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>
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>
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>
400 {
401 constants.clear();
402 }
403
404 template <int lrgst_expo>
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>
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++)
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++)
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_
A class representing an Exponential Function (e.g 2x^2 + 4x - 1),.
Definition Exponential.h:145
std::vector< double > solve_x(const double &y_val, const GA_Options &options=GA_Options()) const
Function that uses a genetic algorithm to find the values of x where y = user value.
Definition Exponential.h:517
double solve_y(const double &x_val) const noexcept
Function that solves for y when x = user value.
Definition Exponential.h:499
friend std::vector< double > QuadraticSolve(const Function< 2 > &f)
Uses the quadratic function to solve the roots of an entered quadratic equation.
Definition Exponential.h:280
std::vector< double > get_real_roots(const GA_Options &options=GA_Options()) const
Function that uses a genetic algorithm to find the approximate roots of the function.
Definition Exponential.h:420
Function< lrgst_expo - 1 > differential() const
Calculates the differential (dy/dx) of the function.
Definition Exponential.h:405
Structure for options to be used when running one of the two genetic algorithms in a Function object.
Definition Exponential.h:22
double min_range
Minimum value you believe the answer can be.
Definition Exponential.h:24
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:28
unsigned int data_size
Amount of solutions you'd like the algorithm to generate (increasing this value causes the algorithm ...
Definition Exponential.h:32
double mutation_percentage
How much you'd like the algorithm to mutate solutions (Leave this as default in most cases)
Definition Exponential.h:34
double max_range
Maximum value you believe the answer can be.
Definition Exponential.h:26
unsigned int sample_size
Amount of approximate solutions you'd like to be returned.
Definition Exponential.h:30