1use fast_ode;
2
3#[derive(Clone, Copy, Debug)]
4pub struct State {
5 pub position_x: f64,
6 pub position_y: f64,
7 pub position_z: f64,
8 pub velocity_x: f64,
9 pub velocity_y: f64,
10 pub velocity_z: f64,
11 pub roll: f64,
12 pub pitch: f64,
13 pub yaw: f64,
14 pub roll_rate: f64,
15 pub pitch_rate: f64,
16 pub yaw_rate: f64,
17}
18
19#[derive(Clone, Copy)]
20pub struct Consts {
21 pub g: f64,
22 pub mass: f64,
23 pub ixx: f64,
24 pub iyy: f64,
25 pub izz: f64,
26}
27
28#[derive(Clone, Copy)]
29pub struct Forces {
30 pub x: f64,
31 pub y: f64,
32 pub z: f64,
33}
34
35#[derive(Clone, Copy)]
36pub struct Torques {
37 pub x: f64,
38 pub y: f64,
39 pub z: f64,
40}
41
42impl State {
43 pub fn to_array(&self) -> [f64; 12] {
44 [
45 self.position_x as f64,
46 self.position_y as f64,
47 self.position_z as f64,
48 self.velocity_x as f64,
49 self.velocity_y as f64,
50 self.velocity_z as f64,
51 self.roll as f64,
52 self.pitch as f64,
53 self.yaw as f64,
54 self.roll_rate as f64,
55 self.pitch_rate as f64,
56 self.yaw_rate as f64,
57 ]
58 }
59
60 pub fn from_array(arr: &[f64; 12]) -> Self {
61 State {
62 position_x: arr[0],
63 position_y: arr[1],
64 position_z: arr[2],
65 velocity_x: arr[3],
66 velocity_y: arr[4],
67 velocity_z: arr[5],
68 roll: arr[6],
69 pitch: arr[7],
70 yaw: arr[8],
71 roll_rate: arr[9],
72 pitch_rate: arr[10],
73 yaw_rate: arr[11],
74 }
75 }
76}
77
78pub struct DroneOde {
79 pub consts: Consts,
80 pub forces: Forces,
81 pub torques: Torques,
82}
83
84impl fast_ode::DifferentialEquation<12> for DroneOde {
85 fn ode_dot_y(&self, _t: f64, y: &fast_ode::Coord<12>) -> (fast_ode::Coord<12>, bool) {
86 let state = y.0;
87
88 let phi = state[6]; let theta = state[7]; let psi = state[8]; let p = state[9]; let q = state[10]; let r = state[11]; let cos_phi = phi.cos();
98 let sin_phi = phi.sin();
99 let cos_theta = theta.cos();
100 let sin_theta = theta.sin();
101 let tan_theta = theta.tan();
102
103 let cos_psi = psi.cos();
105 let sin_psi = psi.sin();
106
107 let fx_body = self.forces.x as f64;
108 let fy_body = self.forces.y as f64;
109 let fz_body = self.forces.z as f64;
110
111 let fx_inertial = (cos_theta * cos_psi) * fx_body
114 + (sin_phi * sin_theta * cos_psi - cos_phi * sin_psi) * fy_body
115 + (cos_phi * sin_theta * cos_psi + sin_phi * sin_psi) * fz_body;
116
117 let fy_inertial = (cos_theta * sin_psi) * fx_body
118 + (sin_phi * sin_theta * sin_psi + cos_phi * cos_psi) * fy_body
119 + (cos_phi * sin_theta * sin_psi - sin_phi * cos_psi) * fz_body;
120
121 let fz_inertial = (-sin_theta) * fx_body
122 + (sin_phi * cos_theta) * fy_body
123 + (cos_phi * cos_theta) * fz_body;
124
125 let mut dot_y = [0.0; 12];
127
128 dot_y[0] = state[3]; dot_y[1] = state[4]; dot_y[2] = state[5]; dot_y[3] = fx_inertial / self.consts.mass as f64; dot_y[4] = fy_inertial / self.consts.mass as f64; dot_y[5] = fz_inertial / self.consts.mass as f64 - self.consts.g as f64; dot_y[6] = p + q * sin_phi * tan_theta + r * cos_phi * tan_theta; dot_y[7] = q * cos_phi - r * sin_phi; dot_y[8] = if cos_theta.abs() > 1e-6 {
142 q * sin_phi / cos_theta + r * cos_phi / cos_theta } else {
144 0.0 };
146
147 let ixx = self.consts.ixx as f64;
149 let iyy = self.consts.iyy as f64;
150 let izz = self.consts.izz as f64;
151
152 dot_y[9] = (self.torques.x as f64 + (iyy - izz) * q * r) / ixx; dot_y[10] = (self.torques.y as f64 + (izz - ixx) * r * p) / iyy; dot_y[11] = (self.torques.z as f64 + (ixx - iyy) * p * q) / izz; (fast_ode::Coord(dot_y), true)
157 }
158}
159
160pub fn simulate_drone(
161 initial_state: State,
162 consts: Consts,
163 forces: Forces,
164 torques: Torques,
165 time_span: (f64, f64),
166 tolerance: f64,
167) -> Result<State, &'static str> {
168 let ode = DroneOde {
169 consts,
170 forces,
171 torques,
172 };
173
174 let initial_coord = fast_ode::Coord(initial_state.to_array());
175
176 let result = fast_ode::solve_ivp(
177 &ode,
178 time_span,
179 initial_coord,
180 |_, _| true,
181 tolerance,
182 tolerance * 10.0,
183 );
184
185 match result {
186 fast_ode::IvpResult::FinalTimeReached(final_coord) => Ok(State::from_array(&final_coord.0)),
187 _ => Err("Integration failed"),
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn test_hover_simulation() {
197 let initial_state = State {
198 position_x: 0.0,
199 position_y: 0.0,
200 position_z: 1.0,
201 velocity_x: 0.0,
202 velocity_y: 0.0,
203 velocity_z: 0.0,
204 roll: 0.0,
205 pitch: 0.0,
206 yaw: 0.0,
207 roll_rate: 0.0,
208 pitch_rate: 0.0,
209 yaw_rate: 0.0,
210 };
211
212 let consts = Consts {
213 g: 9.81,
214 mass: 1.0,
215 ixx: 0.1,
216 iyy: 0.1,
217 izz: 0.2,
218 };
219
220 let forces = Forces {
222 x: 0.0,
223 y: 0.0,
224 z: consts.mass * consts.g, };
226
227 let torques = Torques {
228 x: 0.0,
229 y: 0.0,
230 z: 0.0,
231 };
232
233 let final_state =
234 simulate_drone(initial_state, consts, forces, torques, (0.0, 1.0), 1e-6).unwrap();
235
236 assert!((final_state.position_z - 1.0).abs() < 0.1);
238 assert!(final_state.velocity_z.abs() < 0.1);
239 }
240
241 #[test]
242 fn test_free_fall() {
243 let initial_state = State {
244 position_x: 0.0,
245 position_y: 0.0,
246 position_z: 10.0,
247 velocity_x: 0.0,
248 velocity_y: 0.0,
249 velocity_z: 0.0,
250 roll: 0.0,
251 pitch: 0.0,
252 yaw: 0.0,
253 roll_rate: 0.0,
254 pitch_rate: 0.0,
255 yaw_rate: 0.0,
256 };
257
258 let consts = Consts {
259 g: 9.81,
260 mass: 1.0,
261 ixx: 0.1,
262 iyy: 0.1,
263 izz: 0.2,
264 };
265
266 let forces = Forces {
268 x: 0.0,
269 y: 0.0,
270 z: 0.0,
271 };
272
273 let torques = Torques {
274 x: 0.0,
275 y: 0.0,
276 z: 0.0,
277 };
278
279 let t = 1.0;
280 let final_state =
281 simulate_drone(initial_state, consts, forces, torques, (0.0, t), 1e-6).unwrap();
282
283 let expected_z = 10.0 - 0.5 * 9.81 * t * t;
285 let expected_vz = -9.81 * t;
286
287 assert!((final_state.position_z - expected_z).abs() < 0.1);
288 assert!((final_state.velocity_z - expected_vz).abs() < 0.1);
289 }
290}