catlog/simulate/ode/
kuramoto.rs1use nalgebra::{DMatrix, DVector};
8
9use super::ODESystem;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum KuramotoOrder {
14 First,
16
17 Second,
22}
23
24#[derive(Clone, Debug, PartialEq)]
30pub struct KuramotoSystem {
31 pub order: KuramotoOrder,
33
34 pub coupling_coeffs: DMatrix<f32>,
36
37 pub damping_coeffs: DVector<f32>,
39
40 pub forcing_params: DVector<f32>,
48}
49
50impl KuramotoSystem {
51 pub fn fully_connected_homogeneous(order: KuramotoOrder, forcing_params: DVector<f32>) -> Self {
53 let n = forcing_params.len();
54 let mut coupling_coeffs = DMatrix::from_element(n, n, 1.0);
55 for i in 0..n {
56 coupling_coeffs[(i, i)] = 0.0;
57 }
58 let damping_coeffs = DVector::from_element(n, 1.0);
59 Self {
60 order,
61 coupling_coeffs,
62 damping_coeffs,
63 forcing_params,
64 }
65 }
66}
67
68impl ODESystem for KuramotoSystem {
69 fn vector_field(&self, dx: &mut DVector<f32>, x: &DVector<f32>, _t: f32) {
70 match self.order {
71 KuramotoOrder::First => {
72 let n = x.len();
73 for i in 0..n {
74 let mut rhs = self.forcing_params[i];
75 for j in 0..n {
76 rhs -= self.coupling_coeffs[(i, j)] * (x[i] - x[j]).sin();
77 }
78 dx[i] = rhs / self.damping_coeffs[i];
79 }
80 }
81 KuramotoOrder::Second => {
82 let n = x.len() / 2;
83 for i in 0..n {
84 dx[i] = x[i + n];
85 let mut rhs = self.forcing_params[i] - self.damping_coeffs[i] * x[i + n];
86 for j in 0..n {
87 rhs -= self.coupling_coeffs[(i, j)] * (x[i] - x[j]).sin();
88 }
89 dx[i + n] = rhs;
90 }
91 }
92 }
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use expect_test::expect;
99 use std::f32::consts::{FRAC_PI_2, FRAC_PI_4, TAU as TWO_PI};
100
101 use super::super::{ODEProblem, textplot_mapped_ode_result};
102 use super::*;
103
104 fn angular_distance(t1: f32, t2: f32) -> f32 {
105 let d1 = (t1 - t2) % TWO_PI;
106 let d2 = TWO_PI - d1;
107 d1.min(d2)
108 }
109
110 #[test]
111 fn first_order_kuramoto() {
112 let sys = KuramotoSystem::fully_connected_homogeneous(
113 KuramotoOrder::First,
114 DVector::from_element(3, 10.0 * TWO_PI),
115 );
116 let x0 = DVector::from_column_slice(&[-FRAC_PI_2, 0.0, FRAC_PI_4]);
117 let problem = ODEProblem::new(sys, x0).end_time(1.5);
118
119 let result = problem.solve_rk4(0.001).unwrap();
121 let expected = expect![[r#"
122 ⡳⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 2.4
123 ⠄⠹⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
124 ⠂⠀⠘⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
125 ⡁⠀⠀⠘⢦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
126 ⠄⠀⠀⠀⠈⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
127 ⠂⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
128 ⣁⠀⠀⠀⠀⠀⠀⠳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
129 ⠌⠳⣄⠀⠀⠀⠀⠀⠹⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
130 ⠂⠀⠘⢦⡀⠀⠀⠀⠀⠙⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
131 ⡁⠀⠀⠀⠙⢦⠀⠀⠀⠀⠈⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
132 ⠄⠀⠀⠀⠀⠀⠳⣄⠀⠀⠀⠀⠳⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
133 ⠂⠀⠀⠀⠀⠀⠀⠈⠳⡄⠀⠀⠀⠙⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
134 ⡁⠀⠀⠀⠀⠀⠀⠀⠀⠘⢦⡀⠀⠀⠈⠳⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
135 ⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢦⡀⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
136 ⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠦⣄⠀⠀⠙⢦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
137 ⡁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⢤⡀⠀⠙⠲⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
138 ⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠲⢤⣀⠈⠙⠦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
139 ⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⠲⢤⣀⠉⠒⠦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
140 ⡁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠓⠲⠤⢭⣙⣒⡦⠤⣄⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
141 ⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠙⠒⠚⠛⠻⠷⠶⠶⠶⠦⣤⣤⣤⣤⣤⡀
142 ⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠁ 0.0
143 0.0 1.5
144 "#]];
145 expected.assert_eq(&textplot_mapped_ode_result(
146 &problem,
147 &result,
148 |_i| true,
149 |x, i| angular_distance(x[i], x[0]),
150 ));
151 }
152
153 #[test]
154 fn second_order_kuramoto() {
155 let sys = KuramotoSystem::fully_connected_homogeneous(
156 KuramotoOrder::Second,
157 DVector::from_column_slice(&[1.0, -1.0, 1.0]),
158 );
159 let x0 = DVector::from_column_slice(&[-FRAC_PI_2, 0.0, FRAC_PI_4, 0.0, 0.0, 0.0]);
160 let problem = ODEProblem::new(sys, x0).end_time(10.0);
161
162 let result = problem.solve_rk4(0.01).unwrap();
165 let expected = expect![[r#"
166 ⡙⢦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 2.4
167 ⠄⠈⢧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
168 ⠂⠀⠈⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
169 ⡁⠀⠀⢸⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
170 ⠝⢦⠀⠀⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
171 ⠂⠈⣆⠀⢸⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
172 ⡁⠀⠸⡀⠀⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
173 ⠄⠀⠀⢧⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
174 ⠂⠀⠀⠸⡄⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
175 ⡁⠀⠀⠀⢇⠀⢸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
176 ⠄⠀⠀⠀⢸⡀⠈⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
177 ⠂⠀⠀⠀⠀⡇⠀⢹⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠞⠉⠀⠀⠈⠙⠲⢤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣀⣀⣀⡀⠀⠀⠀
178 ⡉⠉⠉⠉⠉⢹⠉⠉⣏⠉⠉⠉⠉⠉⠉⠉⠉⢉⡟⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠙⠛⠻⠭⠭⠟⠛⠛⠋⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠁
179 ⠄⠀⠀⠀⠀⠈⡇⠀⠸⡄⠀⠀⠀⠀⠀⠀⣠⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
180 ⠂⠀⠀⠀⠀⠀⢱⠀⠀⠱⡀⠀⠀⠀⢀⡴⠃⠀⠀⠀⠀⣀⡤⠤⠤⢤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
181 ⡁⠀⠀⠀⠀⠀⠈⡆⠀⠀⠙⢤⣀⡤⠊⠀⠀⠀⠀⣠⠞⠁⠀⠀⠀⠀⠈⠙⠒⠤⣄⣀⠀⠀⠀⠀⠀⢀⣀⣀⣀⡤⠤⠤⠤⠤⠤⠤⠤⠤⠄
182 ⠄⠀⠀⠀⠀⠀⠀⢹⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡴⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
183 ⠂⠀⠀⠀⠀⠀⠀⠈⣇⠀⠀⠀⠀⠀⠀⠀⢀⠞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
184 ⡁⠀⠀⠀⠀⠀⠀⠀⠘⣆⠀⠀⠀⠀⠀⣠⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
185 ⠄⠀⠀⠀⠀⠀⠀⠀⠀⠘⢦⡀⠀⣠⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
186 ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -1.6
187 0.0 10.0
188 "#]];
189 expected.assert_eq(&textplot_mapped_ode_result(
190 &problem,
191 &result,
192 |i| i < 3,
193 |x, i| x[i] - x[0],
194 ));
195 }
196}