Utopia 2
Framework for studying models of complex & adaptive systems.
Loading...
Searching...
No Matches
Classes | Typedefs | Functions | Variables
Utopia::Models::SimpleFlocking Namespace Reference

Classes

class  AgentState
 An agent's state. More...
 
struct  Infrastructure
 
class  SimpleFlocking
 The SimpleFlocking Model. More...
 

Typedefs

using ModelTypes = Utopia::ModelTypes<>
 Type helper to define types used by the model.
 
using AgentTraits = Utopia::AgentTraits< AgentState, Update::sync >
 Agent traits specialization using the state type.
 

Functions

 BOOST_FIXTURE_TEST_CASE (test_state_interface, Infrastructure, *utf::tolerance(1.e-12))
 Test the AgentState interface.
 
 BOOST_FIXTURE_TEST_CASE (test_state_angles, Infrastructure, *utf::tolerance(1.e-12))
 Check angles are used according to convention.
 
 BOOST_AUTO_TEST_SUITE (test_angles)
 
 BOOST_TEST (constrain_angle(+1.)==+1.)
 Assert the basic working of the regularisation function for angles.
 
 BOOST_TEST (constrain_angle(-1.)==-1.)
 
 BOOST_TEST (constrain_angle(+M_PI)==-M_PI)
 
 BOOST_TEST (constrain_angle(-M_PI)==-M_PI)
 
 BOOST_TEST (constrain_angle(+3 *M_PI)==-M_PI)
 
 BOOST_TEST (constrain_angle(-3 *M_PI)==-M_PI)
 
 BOOST_TEST (constrain_angle(+2 *M_PI)==0.)
 
 BOOST_TEST (constrain_angle(+4 *M_PI)==0.)
 
 BOOST_TEST (constrain_angle(+40 *M_PI)==0.)
 
 BOOST_TEST (constrain_angle(-2 *M_PI)==0.)
 
 BOOST_TEST (constrain_angle(-4 *M_PI)==0.)
 
 BOOST_TEST (constrain_angle(-40 *M_PI)==0.)
 
 BOOST_TEST (constrain_angle(+M_PI+1.)==-M_PI+1.)
 
 BOOST_TEST (constrain_angle(-M_PI - 1.)==+M_PI - 1.)
 
 BOOST_FIXTURE_TEST_CASE (test_random_angle, Infrastructure)
 Assert that there is no bias in the random angle function.
 
 BOOST_AUTO_TEST_SUITE_END ()
 
 BOOST_AUTO_TEST_SUITE (test_geometry)
 
 BOOST_AUTO_TEST_CASE (test_absolute_group_velocity)
 
 BOOST_AUTO_TEST_SUITE (test_circular_stats)
 
 BOOST_AUTO_TEST_CASE (test_circular_mean)
 Tests the circular_mean function.
 
 BOOST_AUTO_TEST_CASE (test_circular_mean_and_std)
 Tests the circular_mean_and_std function.
 
template<class RNG , class T = double>
random_angle (const std::shared_ptr< RNG > &rng)
 Returns a uniformly random angle value in [-π, +π)
 
template<class T >
constrain_angle (T angle)
 Constrains an angle value to interval [-π, +π)
 
template<class Container >
void constrain_angles (Container &angles)
 In-place constrains all angles in a container to interval [-π, +π)
 
template<class Container >
double absolute_group_velocity (const Container &velocities)
 Computes the absolute group velocity from a container of velocity vectors.
 
template<class Container >
auto _circular_sin_cos_sum (const Container &angles)
 Compute sum of sine and cosine values from angles in a container.
 
template<class Container = std::vector<double>>
auto circular_mean (const Container &angles)
 Computes the circular mean from a sample of (constrained) angles.
 
template<class Container = std::vector<double>>
auto circular_mean_and_std (const Container &angles)
 Computes the circular mean and std from a sample of (constrained) angles.
 

Variables

constexpr double TAU = 2*M_PI
 
constexpr double NaN = std::numeric_limits<double>::quiet_NaN()
 

Typedef Documentation

◆ AgentTraits

Agent traits specialization using the state type.

The first template parameter specifies the type of the cell state, the second sets them to be synchronously updated.

◆ ModelTypes

Type helper to define types used by the model.

Function Documentation

◆ _circular_sin_cos_sum()

template<class Container >
auto Utopia::Models::SimpleFlocking::_circular_sin_cos_sum ( const Container angles)

Compute sum of sine and cosine values from angles in a container.

78 {
79 static_assert(
80 std::is_floating_point<typename Container::value_type>::value,
81 "need angles specified as floating-point types!"
82 );
83
84 const auto sin_sum = std::accumulate(
85 angles.begin(), angles.end(), 0.,
86 [](auto a1, auto a2){ return a1 + std::sin(a2); }
87 );
88 const auto cos_sum = std::accumulate(
89 angles.begin(), angles.end(), 0.,
90 [](auto a1, auto a2){ return a1 + std::cos(a2); }
91 );
92
93 return std::make_pair(sin_sum, cos_sum);
94}

◆ absolute_group_velocity()

template<class Container >
double Utopia::Models::SimpleFlocking::absolute_group_velocity ( const Container velocities)

Computes the absolute group velocity from a container of velocity vectors.

Essentially: the 2-norm of the sum of all velocity vectors, divided by the number of vectors. Returns NaN if the given container is empty.

58 {
59 if (velocities.empty()) {
60 return std::numeric_limits<double>::quiet_NaN();
61 }
62
63 using Vec = typename Container::value_type;
64 Vec zero = velocities[0];
65 zero.fill(0.);
66
67 const Vec group_velocity = std::accumulate(
68 velocities.begin(), velocities.end(), zero
69 );
70 return arma::norm(group_velocity, 2) / velocities.size();
71}

◆ BOOST_AUTO_TEST_CASE() [1/3]

Utopia::Models::SimpleFlocking::BOOST_AUTO_TEST_CASE ( test_absolute_group_velocity  )
93{
94 using Vec = arma::Col<double>;
95 using VecCont = std::vector<Vec>;
96
97 const auto zero = Vec(2, arma::fill::zeros);
98 const auto one = Vec(2, arma::fill::ones);
99
100 // Empty container yields NaN
101 BOOST_TEST(std::isnan(absolute_group_velocity(VecCont{})));
102
103 // Zero-sum group velocities
104 BOOST_TEST(
105 absolute_group_velocity(VecCont({zero, zero, zero})) == 0.
106 );
107 BOOST_TEST(
108 absolute_group_velocity(VecCont({zero, -1*one, +1*one})) == 0.
109 );
110
111 // Non-zero sum group velocities
112 BOOST_TEST(
113 absolute_group_velocity(VecCont({one})) == std::sqrt(2.),
114 tt::tolerance(1.e-10)
115 );
116}
double absolute_group_velocity(const Container &velocities)
Computes the absolute group velocity from a container of velocity vectors.
Definition utils.hh:58
BOOST_TEST(constrain_angle(+1.)==+1.)
Assert the basic working of the regularisation function for angles.

◆ BOOST_AUTO_TEST_CASE() [2/3]

Utopia::Models::SimpleFlocking::BOOST_AUTO_TEST_CASE ( test_circular_mean  )

Tests the circular_mean function.

129{
130 const double pi = M_PI;
131
132 // Mean at zero
133 BOOST_TEST(circular_mean({pi/2, -pi/2}) == 0.);
134 BOOST_TEST(circular_mean({pi/2, -pi/2, 0., -1., +1.}) == 0.);
135
136 // Cone of angles not crossing the discontinuity
137 BOOST_TEST(circular_mean({1, 1, 1, 2, 0}) == +1);
138 BOOST_TEST(circular_mean({-1, -1, -1, -2, -0}) == -1);
139
140 // Check for mean value near or at discontinuity (at ±π)
141 BOOST_TEST(circular_mean({.5*pi, -.5*pi}) == 0.);
142 BOOST_TEST(circular_mean({.5001*pi, -.5001*pi}) == -pi);
143 BOOST_TEST(circular_mean({.9*pi, -.9*pi}) == -pi);
144
145 // No values: will return NaN
146 BOOST_TEST(std::isnan(circular_mean({})));
147}
auto circular_mean(const Container &angles)
Computes the circular mean from a sample of (constrained) angles.
Definition utils.hh:108

◆ BOOST_AUTO_TEST_CASE() [3/3]

Utopia::Models::SimpleFlocking::BOOST_AUTO_TEST_CASE ( test_circular_mean_and_std  )

Tests the circular_mean_and_std function.

152{
153 const double pi = M_PI;
154 auto circ_mean = [](const std::vector<double>& angles){
155 return circular_mean_and_std(angles).first;
156 };
157 auto circ_std = [](const std::vector<double>& angles){
158 return circular_mean_and_std(angles).second;
159 };
160
161 // No values: will return NaN
162 BOOST_TEST(std::isnan(circ_mean({})));
163 BOOST_TEST(std::isnan(circ_std({})));
164
165 // Mean is same as in separate function
166 BOOST_TEST(circ_mean({1, 1, 1, 2, 0}) == +1);
167 BOOST_TEST(circ_mean({.5*pi, -.5*pi}) == 0.);
168 BOOST_TEST(circ_mean({.9*pi, -.9*pi}) == -pi);
169
170 // Std. dev. of values distributed near the center of domain, not crossing
171 // the discontinuity
172 BOOST_TEST(circ_std({0., 0., 0.}) == 0.);
173 BOOST_TEST(circ_std({1., 1., 1.}) == 0.);
174 BOOST_TEST(circ_std({-1., -1., -1.}) == 0.);
175
177 circ_std({-1., 0., 1.}) == 0.855515936, tt::tolerance(1.e-9)
178 );
180 circ_std({0, 0.1*pi/2, 0.001*pi, 0.03*pi/2}) ==
181 0.063564063306, tt::tolerance(1.e-9) // result from scipy example
182 );
183
184 // Angles near and crossing the discontinuity (at ±π)
186 circ_std({+pi-1., +pi, +pi+1.}) == 0.855515936, tt::tolerance(1.e-9)
187 );
189 circ_std({-pi-1., -5*pi, -pi+1.}) == 0.855515936, tt::tolerance(1.e-9)
190 );
191
193 circ_std({+pi, -pi+0.1*pi/2, -pi+0.001*pi, -pi+0.03*pi/2}) ==
194 0.063564063306, tt::tolerance(1.e-10) // result from scipy example
195 );
197 circ_std({-pi, -3*pi+0.1*pi/2, +pi+0.001*pi, +5*pi+0.03*pi/2}) ==
198 0.063564063306, tt::tolerance(1.e-10) // result from scipy example
199 );
200}
auto circular_mean_and_std(const Container &angles)
Computes the circular mean and std from a sample of (constrained) angles.
Definition utils.hh:129

◆ BOOST_AUTO_TEST_SUITE() [1/3]

Utopia::Models::SimpleFlocking::BOOST_AUTO_TEST_SUITE ( test_angles  )

◆ BOOST_AUTO_TEST_SUITE() [2/3]

Utopia::Models::SimpleFlocking::BOOST_AUTO_TEST_SUITE ( test_circular_stats  )

◆ BOOST_AUTO_TEST_SUITE() [3/3]

Utopia::Models::SimpleFlocking::BOOST_AUTO_TEST_SUITE ( test_geometry  )

◆ BOOST_AUTO_TEST_SUITE_END()

Utopia::Models::SimpleFlocking::BOOST_AUTO_TEST_SUITE_END ( )

◆ BOOST_FIXTURE_TEST_CASE() [1/3]

Utopia::Models::SimpleFlocking::BOOST_FIXTURE_TEST_CASE ( test_random_angle  ,
Infrastructure   
)

Assert that there is no bias in the random angle function.

This is not to test the properties of uniform_real_distribution or the RNG but of the hard-coded interval in the random_angle function, which should for consistency's sake be symmetric around zero.

68{
69 auto agg_angle = 0.;
70 auto val = 0.;
71 auto N = 100000u;
72
73 for (auto i = 0u; i < N; i++) {
74 val = random_angle(rng);
75 BOOST_TEST(val < +M_PI);
76 BOOST_TEST(val >= -M_PI);
77 agg_angle += val;
78 }
79
80 BOOST_TEST(std::abs(agg_angle/N) < 0.02);
81}
T random_angle(const std::shared_ptr< RNG > &rng)
Returns a uniformly random angle value in [-π, +π)
Definition utils.hh:26

◆ BOOST_FIXTURE_TEST_CASE() [2/3]

Utopia::Models::SimpleFlocking::BOOST_FIXTURE_TEST_CASE ( test_state_angles  ,
Infrastructure  ,
utf::tolerance1.e-12 
)

Check angles are used according to convention.

70{
71 auto state = AgentState(cfg["agent_state"], rng);
72 state.set_speed(1.);
73
74 // Zero: Movement in +x direction
75 state.set_orientation(0.);
76 BOOST_TEST(state.get_displacement()[0] == 1.);
77 BOOST_TEST(state.get_displacement()[1] == 0.);
78
79 // ± π/2: movement in ±y direction
80 state.set_orientation(+M_PI/2.);
81 BOOST_TEST(state.get_displacement()[0] == 0.);
82 BOOST_TEST(state.get_displacement()[1] == +1.);
83
84 state.set_orientation(-M_PI/2.);
85 BOOST_TEST(state.get_displacement()[0] == 0.);
86 BOOST_TEST(state.get_displacement()[1] == -1.);
87
88 // ±M_PI: Movement in -x direction
89 state.set_orientation(+M_PI);
90 BOOST_TEST(state.get_displacement()[0] == -1.);
91 BOOST_TEST(state.get_displacement()[1] == 0.);
92
93 state.set_orientation(-M_PI);
94 BOOST_TEST(state.get_displacement()[0] == -1.);
95 BOOST_TEST(state.get_displacement()[1] == 0.);
96}
An agent's state.
Definition state.hh:15

◆ BOOST_FIXTURE_TEST_CASE() [3/3]

Utopia::Models::SimpleFlocking::BOOST_FIXTURE_TEST_CASE ( test_state_interface  ,
Infrastructure  ,
utf::tolerance1.e-12 
)

Test the AgentState interface.

33{
34 // Default construction
35 auto state = AgentState();
36 BOOST_TEST(state.get_speed() == 0.);
37 BOOST_TEST(state.get_orientation() == 0.);
38 BOOST_TEST(arma::norm(state.get_displacement(), 2) == 0.);
39
40 // Config-based construction and assignment
41 auto agent_cfg = cfg["agent_state"];
42
43 state = AgentState(agent_cfg, rng);
44 BOOST_TEST(state.get_speed() == get_as<double>("speed", agent_cfg));
45 BOOST_TEST(state.get_orientation() != 0.); // random value
46 BOOST_TEST(arma::norm(state.get_displacement(), 2) != 0.);
47
48 // Setting speed or orientation updates the displacement vector
49 auto old_displacement = state.get_displacement();
50 state.set_speed(0.);
51 BOOST_TEST(arma::norm(state.get_displacement(), 2) == 0.);
52 state.set_orientation(0.);
53 BOOST_TEST(arma::norm(state.get_displacement(), 2) == 0.);
54
55 state.set_speed(23.);
56 BOOST_TEST(arma::norm(state.get_displacement(), 2) != 0.);
57
58 // Displacement vector is normalized, but scaled with speed
59 BOOST_TEST(arma::norm(state.get_displacement(), 2) == 23.);
60
61 // Construction also works without speed specified
62 state = AgentState(agent_cfg["i_do_not_exist"], rng);
63 BOOST_TEST(state.get_speed() == 0.);
64}

◆ BOOST_TEST() [1/14]

Utopia::Models::SimpleFlocking::BOOST_TEST ( constrain_angle(+1.)  = =+1.)

Assert the basic working of the regularisation function for angles.

Values should always be in [-π, +π)

◆ BOOST_TEST() [2/14]

Utopia::Models::SimpleFlocking::BOOST_TEST ( constrain_angle(+2 *M_PI = =0.)

◆ BOOST_TEST() [3/14]

Utopia::Models::SimpleFlocking::BOOST_TEST ( constrain_angle(+3 *M_PI = =-M_PI)

◆ BOOST_TEST() [4/14]

Utopia::Models::SimpleFlocking::BOOST_TEST ( constrain_angle(+4 *M_PI = =0.)

◆ BOOST_TEST() [5/14]

Utopia::Models::SimpleFlocking::BOOST_TEST ( constrain_angle(+40 *M_PI = =0.)

◆ BOOST_TEST() [6/14]

Utopia::Models::SimpleFlocking::BOOST_TEST ( constrain_angle(+M_PI = =-M_PI)

◆ BOOST_TEST() [7/14]

Utopia::Models::SimpleFlocking::BOOST_TEST ( constrain_angle(+M_PI+1.)  = =-M_PI+1.)

◆ BOOST_TEST() [8/14]

Utopia::Models::SimpleFlocking::BOOST_TEST ( constrain_angle(-1.)  = =-1.)

◆ BOOST_TEST() [9/14]

Utopia::Models::SimpleFlocking::BOOST_TEST ( constrain_angle(-2 *M_PI = =0.)

◆ BOOST_TEST() [10/14]

Utopia::Models::SimpleFlocking::BOOST_TEST ( constrain_angle(-3 *M_PI = =-M_PI)

◆ BOOST_TEST() [11/14]

Utopia::Models::SimpleFlocking::BOOST_TEST ( constrain_angle(-4 *M_PI = =0.)

◆ BOOST_TEST() [12/14]

Utopia::Models::SimpleFlocking::BOOST_TEST ( constrain_angle(-40 *M_PI = =0.)

◆ BOOST_TEST() [13/14]

Utopia::Models::SimpleFlocking::BOOST_TEST ( constrain_angle(-M_PI - 1.)  = =+M_PI - 1.)

◆ BOOST_TEST() [14/14]

Utopia::Models::SimpleFlocking::BOOST_TEST ( constrain_angle(-M_PI = =-M_PI)

◆ circular_mean()

template<class Container = std::vector<double>>
auto Utopia::Models::SimpleFlocking::circular_mean ( const Container angles)

Computes the circular mean from a sample of (constrained) angles.

Uses circular statistics to compute the mean. Assumes angles to be in radians. While it does not matter in which interval they are, the resulting mean value will be in [-π, +π).

Returns NaN if the given container is empty.

See scipy implementation for reference: https://github.com/scipy/scipy/blob/v1.7.1/scipy/stats/morestats.py#L3474

108 {
109 if (angles.empty()) {
110 return std::numeric_limits<double>::quiet_NaN();
111 }
112
113 const auto&& [sin_sum, cos_sum] = _circular_sin_cos_sum(angles);
114 return constrain_angle(std::atan2(sin_sum, cos_sum));
115}

◆ circular_mean_and_std()

template<class Container = std::vector<double>>
auto Utopia::Models::SimpleFlocking::circular_mean_and_std ( const Container angles)

Computes the circular mean and std from a sample of (constrained) angles.

Uses circular statistics to compute the mean and standard deviation. Assumes angles to be in radians. While it does not matter in which interval they are, the resulting mean value will be in [-π, +π).

Returns NaN if the given container is empty.

See scipy implementation for reference: https://github.com/scipy/scipy/blob/v1.7.1/scipy/stats/morestats.py#L3595

129 {
130 if (angles.empty()) {
131 return std::make_pair(NaN, NaN);
132 }
133
134 const auto&& [sin_sum, cos_sum] = _circular_sin_cos_sum(angles);
135 const auto mean = constrain_angle(std::atan2(sin_sum, cos_sum));
136
137 const auto r = std::min(
138 1., std::sqrt(sin_sum*sin_sum + cos_sum*cos_sum) / angles.size()
139 );
140 const auto std = std::sqrt(-2. * std::log(r));
141
142 return std::make_pair(mean, std);
143}
Definition parallel.hh:235

◆ constrain_angle()

template<class T >
T Utopia::Models::SimpleFlocking::constrain_angle ( angle)

Constrains an angle value to interval [-π, +π)

32 {
33 angle = std::fmod(angle + M_PI, TAU);
34 while (angle < 0.) {
35 angle += TAU;
36 }
37 return angle - M_PI;
38}
constexpr double TAU
Definition utils.hh:18

◆ constrain_angles()

template<class Container >
void Utopia::Models::SimpleFlocking::constrain_angles ( Container angles)

In-place constrains all angles in a container to interval [-π, +π)

42 {
44 angles.begin(), angles.end(), angles.begin(),
45 [](auto angle){ return constrain_angle(angle); }
46 );
47}
OutputIt transform(const Utopia::ExecPolicy policy, InputIt first1, InputIt last1, OutputIt d_first, UnaryOperation unary_op)
Apply a unary operator to a range and store the result in a new range.
Definition parallel.hh:368

◆ random_angle()

template<class RNG , class T = double>
T Utopia::Models::SimpleFlocking::random_angle ( const std::shared_ptr< RNG > &  rng)

Returns a uniformly random angle value in [-π, +π)

26 {
27 return std::uniform_real_distribution<T>(-M_PI, +M_PI)(*rng);
28}

Variable Documentation

◆ NaN

constexpr double Utopia::Models::SimpleFlocking::NaN = std::numeric_limits<double>::quiet_NaN()
constexpr

◆ TAU

constexpr double Utopia::Models::SimpleFlocking::TAU = 2*M_PI
constexpr