|
| 1 | +// -*- C++ -*- |
| 2 | +/*! |
| 3 | + * This file is part of sebsjames/maths, a library of maths code for modern C++ |
| 4 | + * |
| 5 | + * See https://github.com/sebsjames/maths |
| 6 | + * |
| 7 | + * \file |
| 8 | + * |
| 9 | + * A Random walk module. Generate a bee-like random walk, following the description given in: |
| 10 | + * |
| 11 | + * "An Anatomically Constrained Model for Path Integration in the Bee |
| 12 | + * Brain", Current Biology 27, 3069-3085, October 23, 2017 (Stone et al.) |
| 13 | + * |
| 14 | + * DOI: http://dx.doi.org/10.1016/j.cub.2017.08.052 |
| 15 | + * |
| 16 | + * \author Seb James |
| 17 | + * \date 2026 |
| 18 | + */ |
| 19 | +module; |
| 20 | + |
| 21 | +#include <cstdint> |
| 22 | +#include <memory> |
| 23 | +#include <cmath> |
| 24 | +#include <stdexcept> |
| 25 | + |
| 26 | +export module sm.random_walk; |
| 27 | + |
| 28 | +import sm.spline; |
| 29 | +import sm.mathconst; |
| 30 | +import sm.random; |
| 31 | +import sm.algo; |
| 32 | +import sm.vec; |
| 33 | +import sm.vvec; |
| 34 | + |
| 35 | +export namespace sm |
| 36 | +{ |
| 37 | + // Make a randomized path to follow |
| 38 | + template <typename T> |
| 39 | + struct random_walk |
| 40 | + { |
| 41 | + random_walk() {} |
| 42 | + |
| 43 | + random_walk (const std::uint32_t _n_steps, const std::uint32_t _a_tau) |
| 44 | + { |
| 45 | + this->n_steps = _n_steps; |
| 46 | + this->a_tau = _a_tau; |
| 47 | + this->init(); |
| 48 | + } |
| 49 | + |
| 50 | + random_walk (const std::uint32_t _n_steps, const std::uint32_t _a_tau, const T& _kappa) |
| 51 | + { |
| 52 | + this->n_steps = _n_steps; |
| 53 | + this->a_tau = _a_tau; |
| 54 | + this->kappa = _kappa; |
| 55 | + this->init(); |
| 56 | + } |
| 57 | + |
| 58 | + random_walk (const std::uint32_t _n_steps, const std::uint32_t _a_tau, const T& _kappa, const T& acc_max) |
| 59 | + { |
| 60 | + this->n_steps = _n_steps; |
| 61 | + this->a_tau = _a_tau; |
| 62 | + this->kappa = _kappa; |
| 63 | + this->amm.max = acc_max; |
| 64 | + this->init(); |
| 65 | + } |
| 66 | + |
| 67 | + void init() |
| 68 | + { |
| 69 | + this->rVM = std::make_unique<sm::rand_vonmises<T>> (T{0}, kappa); |
| 70 | + this->a.resize (this->n_steps / this->a_tau); |
| 71 | + this->a.randomize(); |
| 72 | + this->a *= (amm.span()); |
| 73 | + this->a += amm.min; |
| 74 | + cubic_spline_expansion (this->a, this->a_tau); |
| 75 | + } |
| 76 | + |
| 77 | + // Reset state |
| 78 | + void reset() |
| 79 | + { |
| 80 | + this->t = 0; |
| 81 | + this->theta = T{0}; |
| 82 | + this->omega = T{0}; |
| 83 | + this->velocity = {}; |
| 84 | + this->speed = T{0}; |
| 85 | + } |
| 86 | + |
| 87 | + void about_turn() { this->theta += sm::mathconst<float>::pi; } |
| 88 | + |
| 89 | + // Advance the route generation by one timestep |
| 90 | + void step() |
| 91 | + { |
| 92 | + // This is the model as stated in the paper and it should be equivalent to lfilter |
| 93 | + // function. |
| 94 | + T epsilon = this->rVM->get(); // Angular acceleration |
| 95 | + this->omega = this->lambda * this->omega + epsilon; |
| 96 | + this->theta += this->omega; |
| 97 | + |
| 98 | + T accel = T{0}; |
| 99 | + if (t < this->a.size()) { accel = this->a[this->t]; } |
| 100 | + |
| 101 | + sm::vec<T, 2> thrust = { accel * std::sin (theta), accel * std::cos (theta) }; |
| 102 | + this->velocity = (this->velocity + thrust) * one_minus_FD; |
| 103 | + this->speed = (this->speed + accel) * one_minus_FD; |
| 104 | + |
| 105 | + ++this->t; |
| 106 | + if (this->t > this->n_steps) { this->reset(); } |
| 107 | + } |
| 108 | + |
| 109 | + // Number of steps total. |
| 110 | + std::uint32_t n_steps = 0; |
| 111 | + // Current time step |
| 112 | + std::uint32_t t = 0; |
| 113 | + |
| 114 | + // State |
| 115 | + T theta = T{0}; // Heading/theta |
| 116 | + T omega = T{0}; // Angular velocity |
| 117 | + sm::vec<T, 2> velocity = {}; // Cartesian velocity (or can use speed): |
| 118 | + T speed = T{0}; // Linear speed |
| 119 | + |
| 120 | + // Parameters |
| 121 | + const T lambda = T{0.4}; |
| 122 | + T kappa = T{100}; // Von Mises concentration parameter |
| 123 | + sm::vvec<T> a = {}; // Acceleration values |
| 124 | + // Uniform RNG range outbound [0, 0.05] |
| 125 | + sm::range<T> amm = { T{0}, T{0.005} }; |
| 126 | + // how often does the acceleration change? |
| 127 | + std::uint32_t a_tau = 50; |
| 128 | + |
| 129 | + // FD is the drag coefficient |
| 130 | + static constexpr T FD = T{0.15}; |
| 131 | + static constexpr T one_minus_FD = (T{1} - FD); |
| 132 | + |
| 133 | + // Random number generation |
| 134 | + std::unique_ptr<sm::rand_vonmises<T>> rVM; |
| 135 | + }; |
| 136 | + |
| 137 | + template <typename T, std::uint32_t N> |
| 138 | + void cubic_spline_expansion (sm::vvec<T>& v, std::uint32_t n) |
| 139 | + { |
| 140 | + if (v.size() != N) { throw std::runtime_error ("v.size != N!"); } |
| 141 | + sm::vec<sm::vec<T, 2>, N> p; |
| 142 | + for (std::uint32_t i = 0; i < N; ++i) { p[i] = { static_cast<T>(n) * i, v[i] }; } |
| 143 | + sm::spline<T, N> spl (p); |
| 144 | + v.resize (v.size() * (n + 1), T{0}); |
| 145 | + T ti = T{0}; |
| 146 | + for (std::uint32_t i = 0; i < v.size(); ++i, ti += T{1}) { v[i] = spl.compute (ti); } |
| 147 | + } |
| 148 | + |
| 149 | + // Place n elements between each element in v, computing a cubic spline interpolation, assuming |
| 150 | + // that the x in f(x) increases by the value 1 with each step. Think of x as t, and there is 1 |
| 151 | + // timestep between each element in the expanded vvec v. This calls fixed-size versions of |
| 152 | + // cubic_spline_expansion due to the limitation from our fixed size sm::mat. |
| 153 | + template <typename T> |
| 154 | + void cubic_spline_expansion (sm::vvec<T>& v, std::uint32_t n) |
| 155 | + { |
| 156 | + // sm::mat is a fixed size matrix template class, so we have to have a switch statement |
| 157 | + switch (v.size()) { |
| 158 | + case 2: |
| 159 | + cubic_spline_expansion<T, 2> (v, n); |
| 160 | + break; |
| 161 | + case 3: |
| 162 | + cubic_spline_expansion<T, 3> (v, n); |
| 163 | + break; |
| 164 | + case 4: |
| 165 | + cubic_spline_expansion<T, 4> (v, n); |
| 166 | + break; |
| 167 | + case 5: |
| 168 | + cubic_spline_expansion<T, 5> (v, n); |
| 169 | + break; |
| 170 | + case 6: |
| 171 | + cubic_spline_expansion<T, 6> (v, n); |
| 172 | + break; |
| 173 | + case 7: |
| 174 | + cubic_spline_expansion<T, 7> (v, n); |
| 175 | + break; |
| 176 | + case 8: |
| 177 | + cubic_spline_expansion<T, 8> (v, n); |
| 178 | + break; |
| 179 | + case 9: |
| 180 | + cubic_spline_expansion<T, 9> (v, n); |
| 181 | + break; |
| 182 | + case 10: |
| 183 | + cubic_spline_expansion<T, 10> (v, n); |
| 184 | + break; |
| 185 | + case 12: |
| 186 | + cubic_spline_expansion<T, 12> (v, n); |
| 187 | + break; |
| 188 | + case 14: |
| 189 | + cubic_spline_expansion<T, 14> (v, n); |
| 190 | + break; |
| 191 | + case 16: |
| 192 | + cubic_spline_expansion<T, 16> (v, n); |
| 193 | + break; |
| 194 | + case 18: |
| 195 | + cubic_spline_expansion<T, 18> (v, n); |
| 196 | + break; |
| 197 | + case 20: |
| 198 | + cubic_spline_expansion<T, 20> (v, n); |
| 199 | + break; |
| 200 | + case 30: |
| 201 | + cubic_spline_expansion<T, 30> (v, n); |
| 202 | + break; |
| 203 | + case 40: |
| 204 | + cubic_spline_expansion<T, 40> (v, n); |
| 205 | + break; |
| 206 | + case 50: |
| 207 | + cubic_spline_expansion<T, 50> (v, n); |
| 208 | + break; |
| 209 | + case 60: |
| 210 | + cubic_spline_expansion<T, 60> (v, n); |
| 211 | + break; |
| 212 | + case 70: |
| 213 | + cubic_spline_expansion<T, 70> (v, n); |
| 214 | + break; |
| 215 | + case 80: |
| 216 | + cubic_spline_expansion<T, 80> (v, n); |
| 217 | + break; |
| 218 | + case 90: |
| 219 | + cubic_spline_expansion<T, 90> (v, n); |
| 220 | + break; |
| 221 | + case 100: |
| 222 | + cubic_spline_expansion<T, 100> (v, n); |
| 223 | + break; |
| 224 | + case 0: |
| 225 | + case 1: |
| 226 | + default: |
| 227 | + throw std::runtime_error ("sm::cubic_spline_expansion cannot handle that sized input"); |
| 228 | + break; |
| 229 | + } |
| 230 | + } |
| 231 | + |
| 232 | +} // namespace |
0 commit comments