Dynamic Neural Field Composer 0.0.0
A C++20 library and interactive application for building and simulating Dynamic Neural Field (DNF) architectures.
Loading...
Searching...
No Matches
math.h
Go to the documentation of this file.
1#pragma once
2
3//https://github.com/stevenlovegrove/Pangolin/issues/352
4#ifdef max
5#undef max
6#endif
7
8#ifdef min
9#undef min
10#endif
11
12#include <vector>
13#include <array>
14#include <cmath>
15//#include <math.h>
16#include <algorithm>
17#include <numeric>
18#include <functional>
19#include <random>
20#include <numbers>
21#include <fstream>
22
24{
25 // https://stackoverflow.com/questions/24518989/how-to-perform-1-dimensional-valid-convolution
26 // The conv function implements the standard convolution method,
27 // where the output size is the sum of input sizes minus one.
28 template<typename T>
29 std::vector<T> conv(std::vector<T> const& f, std::vector<T> const& g)
30 {
31 int const nf = f.size();
32 int const ng = g.size();
33 int const n = nf + ng - 1;
34 std::vector<T> out(n, T());
35 for (auto i(0); i < n; ++i) {
36 int const jmn = (i >= ng - 1) ? i - (ng - 1) : 0;
37 int const jmx = (i < nf - 1) ? i : nf - 1;
38 for (auto j(jmn); j <= jmx; ++j) {
39 out[i] += (f[j] * g[i - j]);
40 }
41 }
42 return out;
43 }
44
45 // conv performs standard convolution, while conv_valid performs valid convolution,
46 // which is generally more efficient for some applications where zero-padded areas
47 // are not desired in the output.
48
49 // https://stackoverflow.com/questions/24518989/how-to-perform-1-dimensional-valid-convolution
50 // The conv_valid function implements the valid convolution method,
51 // where the output size is the absolute difference between the input sizes plus one.
52 template<typename T>
53 std::vector<T> conv_valid(std::vector<T> const& f, std::vector<T> const& g)
54 {
55 int const nf = f.size();
56 int const ng = g.size();
57 std::vector<T> const& min_v = (nf < ng) ? f : g;
58 std::vector<T> const& max_v = (nf < ng) ? g : f;
59 int const n = std::max(nf, ng) - std::min(nf, ng) + 1;
60 std::vector<T> out(n, T());
61 for (auto i(0); i < n; ++i) {
62 for (int j(min_v.size() - 1), k(i); j >= 0; --j) {
63 out[i] += min_v[j] * max_v[k];
64 ++k;
65 }
66 }
67 return out;
68 }
69
70 // ChatGPT 4.0
71 template<typename T>
72 std::vector<T> conv_same(std::vector<T> const& f, std::vector<T> const& g) {
73 int const nf = f.size();
74 int const ng = g.size();
75 std::vector<T> out(nf, T()); // Output size matches the size of f for 'same' mode
76
77 // Calculate padding needed for 'same' mode, adjusted for shift correction
78 const int pad = (ng - 1) / 2;
79
80 for (int i = 0; i < nf; ++i) {
81 T sum = T(); // Initialize the sum for each element of output
82 for (int j = 0; j < ng; ++j) {
83 int fIndex = i + j - pad; // Adjust index for 'same' mode with shift correction
84 if (fIndex >= 0 && fIndex < nf) { // Check boundaries
85 sum += f[fIndex] * g[j];
86 }
87 }
88 out[i] = sum;
89 }
90 return out;
91 }
92
93 // In-place variants — write into a caller-supplied buffer (no heap allocation).
94 // `out` must be pre-sized to the correct output length before calling.
95
96 template<typename T>
97 void conv_valid_into(std::vector<T>& out, const std::vector<T>& f, const std::vector<T>& g)
98 {
99 const int nf = static_cast<int>(f.size());
100 const int ng = static_cast<int>(g.size());
101 const std::vector<T>& min_v = (nf < ng) ? f : g;
102 const std::vector<T>& max_v = (nf < ng) ? g : f;
103 const int n = std::max(nf, ng) - std::min(nf, ng) + 1;
104 for (int i = 0; i < n; ++i) {
105 T acc = T();
106 for (int j = static_cast<int>(min_v.size()) - 1, k = i; j >= 0; --j, ++k)
107 acc += min_v[j] * max_v[k];
108 out[i] = acc;
109 }
110 }
111
112 template<typename T>
113 void conv_same_into(std::vector<T>& out, const std::vector<T>& f, const std::vector<T>& g)
114 {
115 const int nf = static_cast<int>(f.size());
116 const int ng = static_cast<int>(g.size());
117 const int pad = (ng - 1) / 2;
118 for (int i = 0; i < nf; ++i) {
119 T acc = T();
120 for (int j = 0; j < ng; ++j) {
121 const int fIndex = i + j - pad;
122 if (fIndex >= 0 && fIndex < nf)
123 acc += f[fIndex] * g[j];
124 }
125 out[i] = acc;
126 }
127 }
128
129 template<typename T>
130 void obtainCircularVector_into(std::vector<T>& out, const std::vector<int>& indices,
131 const std::vector<T>& contents)
132 {
133 for (int i = 0; i < static_cast<int>(indices.size()); ++i)
134 out[i] = contents[indices[i] - 1];
135 }
136
137 template<typename T>
138 std::vector<T> gaussNorm(const std::vector<int>& rangeX, const T& position, const T& sigma)
139 {
140 std::vector<T> g(rangeX.size());
141 for (int i = 0; i < g.size(); i++)
142 g[i] = exp(-0.5 * pow((rangeX[i] - position), 2) / pow(sigma, 2));
143
144 if (!g.empty())
145 {
146 double sumOfG = std::reduce(g.begin(), g.end());
147 for (int i = 0; i < g.size(); i++)
148 g[i] = g[i] / sumOfG;
149 }
150
151 return g;
152 }
153
154 template<typename T>
155 std::vector<T> gauss(const std::vector<int>& rangeX, const T& position, const T& sigma)
156 {
157 std::vector<T> g(rangeX.size());
158
159 for (int i = 0; i < g.size(); i++)
160 g[i] = exp(-0.5 * pow((rangeX[i] - position), 2) / pow(sigma, 2));
161
162 return g;
163 }
164
165 template<typename T>
166 std::vector<T> gauss(int size, const T& sigma, const T& position)
167 {
168 std::vector<T> g(size);
169
170 for (int i = 0; i < g.size(); i++)
171 {
172 T x = static_cast<T>(i + 1);
173 g[i] = exp(-0.5 * pow((x - position), 2) / pow(sigma, 2));
174 }
175
176 return g;
177 }
178
179 template<typename T>
180 std::vector<T> nonCircularGauss(uint32_t size, const T& sigma, const T& position)
181 {
182 std::vector<T> g(size);
183 std::vector<T> xRange(size);
184 std::iota(xRange.begin(), xRange.end(), static_cast<T>(1));
185
186 for (uint32_t i = 0; i < size; i++)
187 {
188 T distance = static_cast<T>(i + 1) - position;
189 g[i] = std::exp(-0.5 * std::pow(distance, 2) / std::pow(sigma, 2));
190 }
191
192 return g;
193 }
194
195 template<typename T>
196 std::vector<T> circularGauss(uint32_t size, const T& sigma, const T& position)
197 {
198 uint32_t l = size - 2 * 1 + 2;
199 uint32_t m = 1;
200 T r = position - static_cast<T>(m); // Calculate the shift with fractional part
201 T rem = std::fmod(r, static_cast<T>(l)); // Calculate the remainder
202 T positionShifted = rem + static_cast<T>(m); // Apply the shifted position
203
204 std::vector<T> g(size);
205 std::vector<T> xRange(size);
206 std::iota(xRange.begin(), xRange.end(), static_cast<T>(1));
207
208 std::vector<T> d(size);
209 std::vector<T> lMinusd(size);
210 std::transform(xRange.begin(), xRange.end(), d.begin(), [&positionShifted, &l](T element) { return std::abs(element - positionShifted); });
211 std::transform(d.begin(), d.end(), lMinusd.begin(), [&l](T element) { return -1 * (element - l); });
212 for (int i = 0; i < size; i++)
213 {
214 g[i] = std::exp(-0.5 * std::pow(std::min(d[i], lMinusd[i]), 2) / std::pow(sigma, 2));
215 }
216
217 return g;
218 }
219
220 template<typename T>
221 std::vector<T> gaussDerivative(const std::vector<int>& rangeX, const T& position, const T& sigma, const T& amplitude)
222 {
223 std::vector<T> derivative(rangeX.size());
224 T variance = sigma * sigma;
225
226 for (size_t i = 0; i < derivative.size(); i++)
227 {
228 T x = rangeX[i];
229 derivative[i] = -(x - position) / variance * amplitude * std::exp(-0.5 * std::pow((x - position), 2) / variance);
230 }
231
232 return derivative;
233 }
234
235 template<typename T>
236 std::vector<T> gaussDerivativeNorm(const std::vector<int>& rangeX, const T& position, const T& sigma, const T& amplitude)
237 {
238 auto derivative = gaussDerivative(rangeX, position, sigma, amplitude);
239
240 // Normalize by the sum of absolute values to preserve sign
241 T sumOfAbs = std::accumulate(derivative.begin(), derivative.end(), static_cast<T>(0),
242 [](T sum, T value) { return sum + std::abs(value); });
243
244 if (sumOfAbs > static_cast<T>(0))
245 {
246 for (T& value : derivative)
247 value /= sumOfAbs;
248 }
249
250 return derivative;
251 }
252
253 template<typename T>
254 std::vector<T> obtainCircularVector(const std::vector<int>& indices, const std::vector<T>& contents)
255 {
256 std::vector<T> newContents(indices.size());
257 for (int i = 0; i < indices.size(); i++)
258 newContents[i] = contents[indices[i] - 1];
259 return newContents;
260 }
261
262 template <typename T>
263 std::vector<T> sigmoid(const std::vector<T>& x, T beta, T x0)
264 {
265 std::vector<T> s(x.size());
266 for (std::size_t i = 0; i < s.size(); ++i) {
267 s[i] = 1 / (1 + std::exp(-beta * (x[i] - x0)));
268 }
269 return s;
270 }
271
278 template <typename T>
279 std::vector<T> absSigmoid(const std::vector<T>& x, T beta, T x0)
280 {
281 std::vector<T> s(x.size());
282 for (std::size_t i = 0; i < s.size(); ++i) {
283 const T diff = x[i] - x0;
284 s[i] = static_cast<T>(0.5) * (1 + beta * diff / (1 + beta * std::abs(diff)));
285 }
286 return s;
287 }
288
289 template<typename T>
290 std::vector<T> heaviside(const std::vector<T>& x, T threshold)
291 {
292 std::vector<T> h(x.size());
293 for (int i = 0; i < static_cast<int>(h.size()); i++)
294 h[i] = (x[i] > threshold) ? 1 : 0;
295 return h;
296 }
297
298 template<typename T>
299 std::vector<T> sumGauss(const std::vector<T>& gauss1, const std::vector<T>& gauss2)
300 {
301 std::vector<T> gaussResult(gauss1.size());
302 for (int i = 0; i < gauss1.size(); i++)
303 gaussResult[i] = gauss1[i] + gauss2[i];
304 return gaussResult;
305 }
306
307 std::array<int, 2> computeKernelRange(double sigma, int cutOfFactor, int fieldSize, bool circular);
308 std::vector<int> createExtendedIndex(int fieldSize, const std::array<int, 2>& kernelRange);
309
310 std::vector<double> generateNormalVector(int size);
311
312 template <typename T>
313 std::vector<T> normalize(const std::vector<T>& vector)
314 {
315 static constexpr T epsilon = 1e-6;
316 static constexpr T offset = 1.0;
317
318 // Find the minimum and maximum values in the vector
319 const T minVal = *std::ranges::min_element(vector.begin(), vector.end()) - epsilon;
320
321 std::vector<T> normalizedVector = vector;
322 for (T& val : normalizedVector)
323 val += minVal;
324
325 normalizedVector = math::sigmoid(normalizedVector, 0.2, std::abs(minVal) + offset);
326 const T newMinVal = *std::ranges::min_element(normalizedVector.begin(), normalizedVector.end());
327
328 for (T& val : normalizedVector)
329 val -= newMinVal;
330
331 return normalizedVector;
332 }
333
334 template <typename T>
335 std::vector<T> hebbLearningRule(std::vector<T>& weights, const std::vector<T>& input, const std::vector<T>& output, double learningRate)
336 {
337 if (input.empty() || output.empty())
338 throw std::invalid_argument("Input and output vectors cannot be empty");
339
340 const size_t inputSize = input.size();
341 const size_t outputSize = output.size();
342 const size_t expectedSize = inputSize * outputSize;
343
344 if (weights.size() != expectedSize)
345 throw std::invalid_argument("Weight matrix size mismatch");
346
347 for (size_t i = 0; i < inputSize; ++i)
348 {
349 const T scaledInput = learningRate * input[i];
350 const size_t baseIndex = i * outputSize;
351
352 for (size_t j = 0; j < outputSize; ++j)
353 weights[baseIndex + j] += scaledInput * output[j];
354 }
355
356 return weights;
357 }
358
359 template <typename T>
360 std::vector<T> ojaLearningRule(std::vector<T>& weights, const std::vector<T>& input, const std::vector<T>& output, double learningRate)
361 {
362 const int inputSize = input.size();
363 const int outputSize = output.size();
364
365 for (int i = 0; i < inputSize; i++)
366 for (int j = 0; j < outputSize; j++)
367 {
368 int index = i * outputSize + j; // Compute the index for the flattened matrix
369 weights[index] += learningRate * (input[i] * output[j] - output[j] * input[i] * weights[index]);
370 }
371
372 return weights;
373 }
374
375 template <typename T>
376 std::vector<std::vector<T>> deltaLearningRuleWidrowHoff(std::vector<std::vector<T>>& weights, const std::vector<T>& input,
377 const std::vector<T>& actualOutput, const std::vector<T>& targetOutput, double learningRate)
378 {
379 const int inputSize = input.size();
380 const int outputSize = targetOutput.size();
381
382 // Calculate the error between the target output and the actual output
383 std::vector<T> error(outputSize, 0.0);
384 for (size_t j = 0; j < outputSize; ++j) {
385 error[j] = targetOutput[j] - actualOutput[j];
386 }
387
388 // Update the weights based on the error and current activation levels of the fields
389 for (size_t i = 0; i < inputSize; ++i) {
390 for (size_t j = 0; j < outputSize; ++j) {
391 weights[i][j] += learningRate * error[j] * input[i];
392 }
393 }
394
395 return weights;
396 }
397
398 template <typename T>
399 std::vector<std::vector<T>> deltaLearningRuleKroghHertz(std::vector<std::vector<T>>& weights, const std::vector<T>& input,
400 const std::vector<T>& targetOutput, const std::vector<T>& actualOutput,
401 double learningRate)
402 {
403 const int inputSize = input.size();
404 int outputSize = targetOutput.size();
405
407 //std::vector<T> actualOutput(outputSize, 0.0);
408 //for (size_t j = 0; j < outputSize; ++j) {
409 // for (size_t i = 0; i < inputSize; ++i) {
410 // actualOutput[j] += input[i] * weights[i][j];
411 // }
412 //}
413
414 // Calculate the error between the target output and the actual output
415 std::vector<T> error(outputSize, 0.0);
416 for (size_t j = 0; j < outputSize; ++j) {
417 error[j] = targetOutput[j] - actualOutput[j];
418 }
419
420 // Update the weights based on the error and current activation levels of the fields
421 for (size_t i = 0; i < inputSize; ++i) {
422 for (size_t j = 0; j < outputSize; ++j) {
423 //weights[i][j] += learningRate * (error[j] - learningRate * weights[i][j]) * input[i]; // old
424 weights[i][j] += learningRate * (error[j]) * input[i];
425 }
426 }
427
428 return weights;
429 }
430
431 template <typename T>
432 bool compareVectors(const std::vector<T>& vec1, const std::vector<T>& vec2, T threshold) {
433 if (vec1.size() != vec2.size()) {
434 return false; // Vectors are of different sizes, hence not equal
435 }
436
437 for (size_t i = 0; i < vec1.size(); ++i) {
438 if (std::abs(vec1[i] - vec2[i]) > threshold) {
439 return false; // Difference between elements at index i exceeds threshold
440 }
441 }
442
443 return true; // Vectors are equal within the threshold
444 }
445
446 template <typename T>
447 T calculateVectorSum(const std::vector<T>& vec) {
448 T result = 0.0;
449 for (T value : vec) {
450 result += value;
451 }
452 return result;
453 }
454
455 template <typename T>
456 T calculateVectorAvg(const std::vector<T>& vec) {
457 if (vec.empty()) {
458 return T(); // Return default value if vector is empty
459 }
460
461 T sum = T();
462 for (const T& value : vec) {
463 sum += value;
464 }
465 return sum / static_cast<T>(vec.size()); // Calculate average
466 }
467
468 template <typename T>
469 T calculateVectorNorm(const std::vector<T>& vec) {
470 T sum_of_squares = std::accumulate(vec.begin(), vec.end(), 0.0,
471 [](T a, T b) { return a + b * b; });
472 return std::sqrt(sum_of_squares);
473 }
474
475 inline double normalize(const double value, const double min, const double max)
476 {
477 if (value < min) return 0.0;
478 if (value > max) return 1.0;
479 return (value - min) / (max - min);
480 }
481
482 inline double gaussian_2d(double x, double y, double mu_x, double mu_y, double sigma_x, double sigma_y, double A) {
483 const double exponent = -((std::pow(x - mu_x, 2) / (2 * std::pow(sigma_x, 2))) + (std::pow(y - mu_y, 2) / (2 * std::pow(sigma_y, 2))));
484 return A * std::exp(exponent);
485 }
486
487 inline double circular_gaussian_2d(double x, double y, double mu_x, double mu_y, double sigma, double A) {
488 const double exponent = -((std::pow(x - mu_x, 2) + std::pow(y - mu_y, 2)) / (2 * std::pow(sigma, 2)));
489 return A * std::exp(exponent);
490 }
491
492 inline double wrap(double value, double max_value) {
493 if (value < 0) return value + max_value;
494 if (value >= max_value) return value - max_value;
495 return value;
496 }
497
498 inline double gaussian_2d_periodic(double x, double y, double mu_x, double mu_y, double sigma, double A, double max_x, double max_y) {
499 const double xStep = std::min(std::abs(x - mu_x), max_x - std::abs(x - mu_x));
500 const double dy = std::min(std::abs(y - mu_y), max_y - std::abs(y - mu_y));
501 const double exponent = -((std::pow(xStep, 2) + std::pow(dy, 2)) / (2 * std::pow(sigma, 2)));
502 return A * std::exp(exponent);
503 }
504
505 template <typename T>
506 std::vector<T> flattenMatrix(const std::vector<std::vector<T>>& matrix)
507 {
508 const int rows = matrix.size();
509 const int cols = matrix[0].size();
510 std::vector<T> flat_matrix(rows * cols);
511 for (int i = 0; i < rows; ++i)
512 for (int j = 0; j < cols; ++j)
513 flat_matrix[i * cols + j] = static_cast<T>(matrix[i][j]);
514 return flat_matrix;
515 }
516
517 // Linear interpolation resampling from input.size() to outputSize.
518 template<typename T>
519 std::vector<T> resample(const std::vector<T>& input, int outputSize)
520 {
521 if (input.empty() || outputSize <= 0) return {};
522 const int N = static_cast<int>(input.size());
523 if (N == outputSize) return input;
524 if (outputSize == 1) return { input[N / 2] };
525 std::vector<T> out(outputSize);
526 for (int i = 0; i < outputSize; ++i)
527 {
528 const double pos = static_cast<double>(i) * (N - 1) / (outputSize - 1);
529 const int lo = static_cast<int>(pos);
530 const int hi = std::min(lo + 1, N - 1);
531 const double t = pos - lo;
532 out[i] = input[lo] * (1.0 - t) + input[hi] * t;
533 }
534 return out;
535 }
536
537 // In-place variant: writes into a pre-sized output buffer to avoid heap allocation.
538 template<typename T>
539 void resampleInto(const std::vector<T>& input, std::vector<T>& output)
540 {
541 const int N = static_cast<int>(input.size());
542 const int outputSize = static_cast<int>(output.size());
543 if (input.empty() || outputSize <= 0) return;
544 if (N == outputSize) { std::copy(input.begin(), input.end(), output.begin()); return; }
545 if (outputSize == 1) { output[0] = input[N / 2]; return; }
546 for (int i = 0; i < outputSize; ++i)
547 {
548 const double pos = static_cast<double>(i) * (N - 1) / (outputSize - 1);
549 const int lo = static_cast<int>(pos);
550 const int hi = std::min(lo + 1, N - 1);
551 const double t = pos - lo;
552 output[i] = input[lo] * (1.0 - t) + input[hi] * t;
553 }
554 }
555
556 template<typename T>
557 void resampleNearestInto(const std::vector<T>& input, std::vector<T>& output)
558 {
559 const int N = static_cast<int>(input.size());
560 const int M = static_cast<int>(output.size());
561 if (input.empty() || M <= 0) return;
562 if (N == M) { std::copy(input.begin(), input.end(), output.begin()); return; }
563 if (M == 1) { output[0] = input[N / 2]; return; }
564 for (int i = 0; i < M; ++i)
565 {
566 const double pos = static_cast<double>(i) * (N - 1) / (M - 1);
567 output[i] = input[static_cast<int>(std::round(pos))];
568 }
569 }
570
571 // Catmull-Rom cubic spline resampling.
572 template<typename T>
573 void resampleCubicInto(const std::vector<T>& input, std::vector<T>& output)
574 {
575 const int N = static_cast<int>(input.size());
576 const int M = static_cast<int>(output.size());
577 if (input.empty() || M <= 0) return;
578 if (N == M) { std::copy(input.begin(), input.end(), output.begin()); return; }
579 if (M == 1) { output[0] = input[N / 2]; return; }
580 const auto clamp = [N](int idx) { return std::max(0, std::min(idx, N - 1)); };
581 for (int i = 0; i < M; ++i)
582 {
583 const double pos = static_cast<double>(i) * (N - 1) / (M - 1);
584 const int lo = static_cast<int>(pos);
585 const double t = pos - lo;
586 const double p0 = static_cast<double>(input[clamp(lo - 1)]);
587 const double p1 = static_cast<double>(input[clamp(lo)]);
588 const double p2 = static_cast<double>(input[clamp(lo + 1)]);
589 const double p3 = static_cast<double>(input[clamp(lo + 2)]);
590 output[i] = static_cast<T>(0.5 * (2.0 * p1 + (-p0 + p2) * t +
591 (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t * t +
592 (-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t * t * t));
593 }
594 }
595
596 // Separable 2D convolution.
597 // field is stored y-major: field[y * size_x + x].
598 // Applies kernel_x along the x-axis and kernel_y along the y-axis using two 1D passes.
599 // extIndex_x / extIndex_y are the circular extension indices produced by createExtendedIndex
600 // (pass empty vectors for non-circular mode).
601 template<typename T>
602 std::vector<T> conv2d_separable(
603 const std::vector<T>& field,
604 const std::vector<T>& kernel_x,
605 const std::vector<T>& kernel_y,
606 int size_x, int size_y,
607 const std::vector<int>& extIndex_x,
608 const std::vector<int>& extIndex_y)
609 {
610 const bool circular_x = !extIndex_x.empty();
611 const bool circular_y = !extIndex_y.empty();
612
613 // x-pass: convolve each row (fixed y) along the x-axis with kernel_x
614 std::vector<T> tmp(size_x * size_y, T());
615 for (int y = 0; y < size_y; ++y)
616 {
617 std::vector<T> row(size_x);
618 for (int x = 0; x < size_x; ++x)
619 row[x] = field[y * size_x + x];
620
621 std::vector<T> convRow;
622 if (circular_x)
623 {
624 const auto extRow = obtainCircularVector(extIndex_x, row);
625 convRow = conv_valid(extRow, kernel_x);
626 }
627 else
628 {
629 convRow = conv_same(row, kernel_x);
630 }
631 for (int x = 0; x < size_x; ++x)
632 tmp[y * size_x + x] = convRow[x];
633 }
634
635 // y-pass: convolve each column (fixed x) along the y-axis with kernel_y
636 std::vector<T> result(size_x * size_y, T());
637 for (int x = 0; x < size_x; ++x)
638 {
639 std::vector<T> col(size_y);
640 for (int y = 0; y < size_y; ++y)
641 col[y] = tmp[y * size_x + x];
642
643 std::vector<T> convCol;
644 if (circular_y)
645 {
646 const auto extCol = obtainCircularVector(extIndex_y, col);
647 convCol = conv_valid(extCol, kernel_y);
648 }
649 else
650 {
651 convCol = conv_same(col, kernel_y);
652 }
653 for (int y = 0; y < size_y; ++y)
654 result[y * size_x + x] = convCol[y];
655 }
656
657 return result;
658 }
659
660 // In-place 2D separable convolution — writes into caller-supplied buffers.
661 // `out` and `tmp` must be pre-sized to size_x * size_y before calling.
662 template<typename T>
664 std::vector<T>& out,
665 std::vector<T>& tmp,
666 const std::vector<T>& field,
667 const std::vector<T>& kernel_x,
668 const std::vector<T>& kernel_y,
669 int size_x, int size_y,
670 const std::vector<int>& extIndex_x,
671 const std::vector<int>& extIndex_y)
672 {
673 const bool circular_x = !extIndex_x.empty();
674 const bool circular_y = !extIndex_y.empty();
675
676 std::vector<T> row(size_x);
677 std::vector<T> col(size_y);
678 std::vector<T> convRow(size_x);
679 std::vector<T> extRow(circular_x ? extIndex_x.size() : 0);
680 std::vector<T> convCol(size_y);
681 std::vector<T> extCol(circular_y ? extIndex_y.size() : 0);
682
683 // x-pass: convolve each row (fixed y) with kernel_x
684 for (int y = 0; y < size_y; ++y)
685 {
686 for (int x = 0; x < size_x; ++x)
687 row[x] = field[y * size_x + x];
688
689 if (circular_x)
690 {
691 obtainCircularVector_into(extRow, extIndex_x, row);
692 conv_valid_into(convRow, extRow, kernel_x);
693 }
694 else
695 {
696 conv_same_into(convRow, row, kernel_x);
697 }
698 for (int x = 0; x < size_x; ++x)
699 tmp[y * size_x + x] = convRow[x];
700 }
701
702 // y-pass: convolve each column (fixed x) with kernel_y
703 for (int x = 0; x < size_x; ++x)
704 {
705 for (int y = 0; y < size_y; ++y)
706 col[y] = tmp[y * size_x + x];
707
708 if (circular_y)
709 {
710 obtainCircularVector_into(extCol, extIndex_y, col);
711 conv_valid_into(convCol, extCol, kernel_y);
712 }
713 else
714 {
715 conv_same_into(convCol, col, kernel_y);
716 }
717 for (int y = 0; y < size_y; ++y)
718 out[y * size_x + x] = convCol[y];
719 }
720 }
721
722 // Reduction operation used when collapsing one axis of a 2D buffer.
723 enum class ReduceOp { SUM, AVERAGE, MAXIMUM, MINIMUM };
724
725 // Collapse a y-major 2D buffer (field[y * size_x + x]) along one axis into a 1D
726 // result, writing into a pre-sized output buffer.
727 // keepX == true: output has size_x entries; each out[x] reduces over all y.
728 // keepX == false: output has size_y entries; each out[y] reduces over all x.
729 template<typename T>
730 void reduce2DAxis_into(std::vector<T>& out, const std::vector<T>& field,
731 int size_x, int size_y, bool keepX, ReduceOp op)
732 {
733 // Reject non-positive dimensions or a field smaller than size_x*size_y
734 // (malformed dimensions / JSON) before sizing or indexing: produce an
735 // empty result rather than over-allocating or reading out of bounds.
736 if (size_x <= 0 || size_y <= 0 ||
737 field.size() < static_cast<std::size_t>(size_x) * static_cast<std::size_t>(size_y))
738 {
739 out.clear();
740 return;
741 }
742
743 const int outSize = keepX ? size_x : size_y;
744 const int reduceCount = keepX ? size_y : size_x;
745 if (out.size() != static_cast<std::size_t>(outSize)) out.assign(outSize, T());
746
747 for (int o = 0; o < outSize; ++o)
748 {
749 // First sample of the reduced line.
750 auto sampleAt = [&](int k) -> T {
751 const int x = keepX ? o : k;
752 const int y = keepX ? k : o;
753 return field[static_cast<std::size_t>(y) * size_x + x];
754 };
755 T acc = sampleAt(0);
756 for (int k = 1; k < reduceCount; ++k)
757 {
758 const T v = sampleAt(k);
759 switch (op)
760 {
761 case ReduceOp::SUM:
762 case ReduceOp::AVERAGE: acc += v; break;
763 case ReduceOp::MAXIMUM: acc = std::max(acc, v); break;
764 case ReduceOp::MINIMUM: acc = std::min(acc, v); break;
765 }
766 }
767 if (op == ReduceOp::AVERAGE)
768 acc /= static_cast<T>(reduceCount);
769 out[o] = acc;
770 }
771 }
772
773 // Broadcast a 1D profile into a y-major 2D buffer (out[y * size_x + x]), writing
774 // into a pre-sized output buffer.
775 // alongX == true: profile indexes x (size must be size_x); repeated for every y.
776 // alongX == false: profile indexes y (size must be size_y); repeated for every x.
777 template<typename T>
778 void broadcast1DTo2D_into(std::vector<T>& out, const std::vector<T>& profile,
779 int size_x, int size_y, bool alongX)
780 {
781 // Reject non-positive dimensions (malformed dimensions / JSON) before sizing:
782 // a negative size would wrap to an enormous allocation.
783 if (size_x <= 0 || size_y <= 0) { out.clear(); return; }
784
785 const std::size_t total = static_cast<std::size_t>(size_x) * static_cast<std::size_t>(size_y);
786 if (out.size() != total) out.assign(total, T());
787 if (profile.empty()) { std::fill(out.begin(), out.end(), T()); return; }
788 const int profileSize = static_cast<int>(profile.size());
789 for (int y = 0; y < size_y; ++y)
790 for (int x = 0; x < size_x; ++x)
791 {
792 // Clamp the profile index so a mismatched profile size cannot read OOB.
793 const int idx = std::min(alongX ? x : y, profileSize - 1);
794 out[static_cast<std::size_t>(y) * size_x + x] = profile[idx];
795 }
796 }
797}
Definition math.h:24
std::vector< T > circularGauss(uint32_t size, const T &sigma, const T &position)
Definition math.h:196
std::vector< T > hebbLearningRule(std::vector< T > &weights, const std::vector< T > &input, const std::vector< T > &output, double learningRate)
Definition math.h:335
void resampleNearestInto(const std::vector< T > &input, std::vector< T > &output)
Definition math.h:557
void conv2d_separable_into(std::vector< T > &out, std::vector< T > &tmp, const std::vector< T > &field, const std::vector< T > &kernel_x, const std::vector< T > &kernel_y, int size_x, int size_y, const std::vector< int > &extIndex_x, const std::vector< int > &extIndex_y)
Definition math.h:663
std::vector< std::vector< T > > deltaLearningRuleKroghHertz(std::vector< std::vector< T > > &weights, const std::vector< T > &input, const std::vector< T > &targetOutput, const std::vector< T > &actualOutput, double learningRate)
Definition math.h:399
std::vector< T > sigmoid(const std::vector< T > &x, T beta, T x0)
Definition math.h:263
T calculateVectorNorm(const std::vector< T > &vec)
Definition math.h:469
void resampleCubicInto(const std::vector< T > &input, std::vector< T > &output)
Definition math.h:573
T calculateVectorAvg(const std::vector< T > &vec)
Definition math.h:456
std::vector< T > conv_same(std::vector< T > const &f, std::vector< T > const &g)
Definition math.h:72
std::vector< T > ojaLearningRule(std::vector< T > &weights, const std::vector< T > &input, const std::vector< T > &output, double learningRate)
Definition math.h:360
std::vector< T > resample(const std::vector< T > &input, int outputSize)
Definition math.h:519
std::vector< T > gaussDerivative(const std::vector< int > &rangeX, const T &position, const T &sigma, const T &amplitude)
Definition math.h:221
void broadcast1DTo2D_into(std::vector< T > &out, const std::vector< T > &profile, int size_x, int size_y, bool alongX)
Definition math.h:778
std::vector< int > createExtendedIndex(int fieldSize, const std::array< int, 2 > &kernelRange)
Definition math.cpp:29
ReduceOp
Definition math.h:723
std::vector< T > conv2d_separable(const std::vector< T > &field, const std::vector< T > &kernel_x, const std::vector< T > &kernel_y, int size_x, int size_y, const std::vector< int > &extIndex_x, const std::vector< int > &extIndex_y)
Definition math.h:602
double gaussian_2d(double x, double y, double mu_x, double mu_y, double sigma_x, double sigma_y, double A)
Definition math.h:482
std::vector< T > gaussDerivativeNorm(const std::vector< int > &rangeX, const T &position, const T &sigma, const T &amplitude)
Definition math.h:236
std::vector< double > generateNormalVector(int size)
Definition math.cpp:53
std::vector< T > obtainCircularVector(const std::vector< int > &indices, const std::vector< T > &contents)
Definition math.h:254
double wrap(double value, double max_value)
Definition math.h:492
double circular_gaussian_2d(double x, double y, double mu_x, double mu_y, double sigma, double A)
Definition math.h:487
std::vector< T > normalize(const std::vector< T > &vector)
Definition math.h:313
T calculateVectorSum(const std::vector< T > &vec)
Definition math.h:447
void reduce2DAxis_into(std::vector< T > &out, const std::vector< T > &field, int size_x, int size_y, bool keepX, ReduceOp op)
Definition math.h:730
double gaussian_2d_periodic(double x, double y, double mu_x, double mu_y, double sigma, double A, double max_x, double max_y)
Definition math.h:498
std::vector< std::vector< T > > deltaLearningRuleWidrowHoff(std::vector< std::vector< T > > &weights, const std::vector< T > &input, const std::vector< T > &actualOutput, const std::vector< T > &targetOutput, double learningRate)
Definition math.h:376
std::vector< T > conv_valid(std::vector< T > const &f, std::vector< T > const &g)
Definition math.h:53
std::vector< T > flattenMatrix(const std::vector< std::vector< T > > &matrix)
Definition math.h:506
std::vector< T > nonCircularGauss(uint32_t size, const T &sigma, const T &position)
Definition math.h:180
std::vector< T > sumGauss(const std::vector< T > &gauss1, const std::vector< T > &gauss2)
Definition math.h:299
void conv_same_into(std::vector< T > &out, const std::vector< T > &f, const std::vector< T > &g)
Definition math.h:113
std::vector< T > conv(std::vector< T > const &f, std::vector< T > const &g)
Definition math.h:29
void conv_valid_into(std::vector< T > &out, const std::vector< T > &f, const std::vector< T > &g)
Definition math.h:97
bool compareVectors(const std::vector< T > &vec1, const std::vector< T > &vec2, T threshold)
Definition math.h:432
std::array< int, 2 > computeKernelRange(double sigma, int cutOfFactor, int fieldSize, bool circular)
Definition math.cpp:10
std::vector< T > heaviside(const std::vector< T > &x, T threshold)
Definition math.h:290
void resampleInto(const std::vector< T > &input, std::vector< T > &output)
Definition math.h:539
std::vector< T > gaussNorm(const std::vector< int > &rangeX, const T &position, const T &sigma)
Definition math.h:138
void obtainCircularVector_into(std::vector< T > &out, const std::vector< int > &indices, const std::vector< T > &contents)
Definition math.h:130
std::vector< T > gauss(const std::vector< int > &rangeX, const T &position, const T &sigma)
Definition math.h:155
std::vector< T > absSigmoid(const std::vector< T > &x, T beta, T x0)
AbsSigmoid: rational approximation of the logistic sigmoid.
Definition math.h:279