1use anyhow::anyhow;
2use tch::IndexOp;
3use tch::Tensor;
4
5pub mod optimizers;
6
7pub fn solve_euler(
9 f: tch::CModule,
10 x_span: Tensor,
11 y0: Tensor,
12 step: Tensor,
13) -> anyhow::Result<(Tensor, Tensor)> {
14 if x_span.size() != [2] {
15 return Err(anyhow!("x_span must be of shape [2]"));
16 }
17 if y0.size().len() != 1 {
18 return Err(anyhow!("y0 must be a one-dimensional tensor"));
19 }
20 if step.size().len() != 0 {
21 return Err(anyhow!("step must be a zero-dimensional tensor"));
22 }
23
24 let x_start = x_span.i(0);
25 let x_end = x_span.i(1);
26
27 let mut x = x_start.unsqueeze(0);
28 let mut y = y0.unsqueeze(0);
29
30 let mut all_x = vec![x.copy()];
31 let mut all_y = vec![y.copy()];
32
33 let mut current_step = step;
34 while x.lt_tensor(&x_end) == Tensor::from_slice(&[true]) {
35 let remaining = &x_end - &x.squeeze();
36 if remaining.lt_tensor(¤t_step) == Tensor::from(true) {
37 current_step = remaining.copy();
38 }
39
40 let dy = f.forward_ts(&[x.squeeze().copy(), y.squeeze().copy()])?;
41 y = &y + ¤t_step * &dy;
42 x = &x + ¤t_step;
43
44 all_x.push(x.copy());
45 all_y.push(y.copy());
46 }
47
48 Ok((Tensor::cat(&all_x, 0), Tensor::cat(&all_y, 0)))
49}
50
51pub fn solve_rk4(
53 f: tch::CModule,
54 x_span: Tensor,
55 y0: Tensor,
56 step: Tensor,
57) -> anyhow::Result<(Tensor, Tensor)> {
58 if x_span.size() != [2] {
59 return Err(anyhow!("x_span must be of shape [2]"));
60 }
61 if y0.size().len() != 1 {
62 return Err(anyhow!("y0 must be a one-dimensional tensor"));
63 }
64 if step.size().len() != 0 {
65 return Err(anyhow!("step must be a zero-dimensional tensor"));
66 }
67
68 let x_start = x_span.i(0);
69 let x_end = x_span.i(1);
70
71 let mut x = x_start.unsqueeze(0);
72 let mut y = y0.unsqueeze(0);
73
74 let mut all_x = vec![x.copy()];
75 let mut all_y = vec![y.copy()];
76
77 let mut current_step = step;
78 while x.lt_tensor(&x_end) == Tensor::from_slice(&[true]) {
79 let remaining = &x_end - &x.squeeze();
80 if remaining.lt_tensor(¤t_step) == Tensor::from(true) {
81 current_step = remaining.copy();
82 }
83
84 let k1 = f.forward_ts(&[x.squeeze().copy(), y.squeeze().copy()])?;
85
86 let x_half: Tensor = &x + 0.5 * ¤t_step;
87 let y_half: Tensor = &y + 0.5 * ¤t_step * &k1;
88 let k2 = f.forward_ts(&[x_half.squeeze(), y_half.squeeze()])?;
89
90 let x_half_again: Tensor = &x + 0.5 * ¤t_step;
91 let y_half_again: Tensor = &y + 0.5 * ¤t_step * &k2;
92 let k3 = f.forward_ts(&[x_half_again.squeeze(), y_half_again.squeeze()])?;
93
94 let x_full = &x + ¤t_step;
95 let y_full = &y + ¤t_step * &k3;
96 let k4 = f.forward_ts(&[x_full.squeeze(), y_full.squeeze()])?;
97
98 let step_div_6 = ¤t_step / 6.0;
99 let y_next = &y + &step_div_6 * (&k1 + 2.0 * &k2 + 2.0 * &k3 + &k4);
100
101 x = &x + ¤t_step;
102 y = y_next;
103
104 all_x.push(x.copy());
105 all_y.push(y.copy());
106 }
107
108 Ok((Tensor::cat(&all_x, 0), Tensor::cat(&all_y, 0)))
109}
110
111pub fn solve_implicit_euler(
113 f: tch::CModule,
114 x_span: Tensor,
115 y0: Tensor,
116 step: Tensor,
117 optimizer: &dyn optimizers::Optimizer,
118) -> anyhow::Result<(Tensor, Tensor)> {
119 if x_span.size() != [2] {
120 return Err(anyhow!("x_span must be of shape [2]"));
121 }
122 if y0.size().len() != 1 {
123 return Err(anyhow!("y0 must be a one-dimensional tensor"));
124 }
125 if step.size().len() != 0 {
126 return Err(anyhow!("step must be a zero-dimensional tensor"));
127 }
128
129 let x_start = x_span.i(0);
130 let x_end = x_span.i(1);
131
132 let mut x = x_start.unsqueeze(0);
133 let mut y = y0.unsqueeze(0);
134
135 let mut all_x = vec![x.copy()];
136 let mut all_y = vec![y.copy()];
137
138 let mut current_step = step;
139 while x.lt_tensor(&x_end) == Tensor::from_slice(&[true]) {
140 let remaining = &x_end - &x.squeeze();
141 if remaining.lt_tensor(¤t_step) == Tensor::from(true) {
142 current_step = remaining.copy();
143 }
144
145 let x_next = &x + ¤t_step;
146 let y_prev = y.copy();
147
148 let y_next = optimizer.optimize(
149 &|y_next: &Tensor| {
150 let f_next = f
151 .forward_ts(&[x_next.squeeze().copy(), y_next.squeeze().copy()])
152 .unwrap();
153 let y_pred = &y_prev.squeeze() + ¤t_step * &f_next;
154 (y_next - &y_pred).pow_tensor_scalar(2).sum(y_next.kind())
155 },
156 &(&y_prev.detach().squeeze()
157 + ¤t_step * f.forward_ts(&[&x.squeeze(), &y_prev.squeeze()])?),
158 ).map_err( |err| {
159 anyhow!(format!("Optimizer failed with: {}", err))
160 })?;
161
162 y = y_next.unsqueeze(0);
163 x = x_next.copy();
164
165 all_x.push(x.copy());
166 all_y.push(y.copy());
167 }
168
169 Ok((Tensor::cat(&all_x, 0), Tensor::cat(&all_y, 0)))
170}
171
172pub fn solve_glrk4(
174 f: tch::CModule,
175 x_span: Tensor,
176 y0: Tensor,
177 step: Tensor,
178 optimizer: &dyn optimizers::Optimizer,
179) -> anyhow::Result<(Tensor, Tensor)> {
180 if x_span.size() != [2] {
181 return Err(anyhow!("x_span must be of shape [2]"));
182 }
183 if y0.size().len() != 1 {
184 return Err(anyhow!("y0 must be a one-dimensional tensor"));
185 }
186 if step.size().len() != 0 {
187 return Err(anyhow!("step must be a zero-dimensional tensor"));
188 }
189
190 let x_start = x_span.i(0);
191 let x_end = x_span.i(1);
192
193 let mut x = x_start.unsqueeze(0);
194 let mut y = y0.unsqueeze(0);
195
196 let mut all_x = vec![x.copy()];
197 let mut all_y = vec![y.copy()];
198
199 let mut current_step = step;
200 while x.lt_tensor(&x_end) == Tensor::from_slice(&[true]) {
201 let remaining = &x_end - &x.squeeze();
202 if remaining.lt_tensor(¤t_step) == Tensor::from(true) {
203 current_step = remaining.copy();
204 }
205
206 let k = f.forward_ts(&[x.squeeze().copy(), y.squeeze().copy()])?;
207
208 const C1: f64 = 0.2113248654f64;
209 const C2: f64 = 0.7886751346f64;
210 const A11: f64 = 0.25;
211 const A12: f64 = -0.03867513459f64;
212 const A21: f64 = 0.5386751346f64;
213 const A22: f64 = 0.25;
214
215 let first_k1k2_guess = Tensor::cat(
216 &[
217 f.forward_ts(&[
218 &x.squeeze() + C1 * ¤t_step,
219 &y.squeeze() + C1 * ¤t_step * &k,
220 ])?,
221 f.forward_ts(&[
222 &x.squeeze() + C2 * ¤t_step,
223 &y.squeeze() + C2 * ¤t_step * &k,
224 ])?,
225 ],
226 0,
227 );
228 let k1k2 = optimizer.optimize(
229 &|k1k2_guess| {
230 let diff1 = k1k2_guess.i(0..=1)
231 - f.forward_ts(&[
232 &x.squeeze() + C1 * ¤t_step,
233 &y.squeeze()
234 + (A11 * k1k2_guess.i(0..=1) + A12 * k1k2_guess.i(2..=3))
235 * ¤t_step,
236 ])
237 .unwrap();
238 let diff2 = k1k2_guess.i(2..=3)
239 - f.forward_ts(&[
240 &x.squeeze() + C2 * ¤t_step,
241 &y.squeeze()
242 + (A21 * k1k2_guess.i(0..=1) + A22 * k1k2_guess.i(2..=3))
243 * ¤t_step,
244 ])
245 .unwrap();
246
247 diff1.dot(&diff1) + diff2.dot(&diff2)
248 },
249 &first_k1k2_guess,
250 ).map_err( |err| {
251 anyhow!(format!("Optimizer failed with: {}", err))
252 })?;
253 assert!(k1k2.size().len() == 1);
254 assert!(k1k2.size()[0] == 4);
255
256 x = &x + ¤t_step;
257 y = &y + ¤t_step * (0.5 * k1k2.i(0..=1) + 0.5 * k1k2.i(2..=3));
258
259 all_x.push(x.copy());
260 all_y.push(y.copy());
261 }
262
263 Ok((Tensor::cat(&all_x, 0), Tensor::cat(&all_y, 0)))
264}
265
266pub fn solve_rkf45(
268 f: tch::CModule,
269 x_span: Tensor,
270 y0: Tensor,
271 rtol: Tensor,
272 atol: Tensor,
273 min_step: Tensor,
274 safety_factor: f64,
275) -> anyhow::Result<(Tensor, Tensor)> {
276 if x_span.size() != [2] {
277 return Err(anyhow!("x_span must be of shape [2]"));
278 }
279 if y0.size().len() != 1 {
280 return Err(anyhow!("y0 must be a one-dimensional tensor"));
281 }
282 if rtol.size().len() != 0 || atol.size().len() != 0 || min_step.size().len() != 0 {
283 return Err(anyhow!("rtol, atol, and min_step must be scalar tensors"));
284 }
285
286 let x_start = x_span.i(0);
287 let x_end = x_span.i(1);
288
289 let mut x = x_start.unsqueeze(0);
290 let mut y = y0.unsqueeze(0);
291
292 let mut all_x = vec![x.copy()];
293 let mut all_y = vec![y.copy()];
294
295 let mut step = (&x_end - &x_start) * 0.1;
296 let safety_factor_tensor = Tensor::from(safety_factor);
297
298 while x.lt_tensor(&x_end) == Tensor::from_slice(&[true]) {
299 let remaining = &x_end - &x.squeeze();
300 if remaining.lt_tensor(&step) == Tensor::from(true) {
301 step = remaining.copy();
302 }
303
304 let k1 = f.forward_ts(&[x.squeeze().copy(), y.squeeze().copy()])?;
305
306 let k2 = {
307 let x_step: Tensor = &x + 0.25 * &step;
308 let y_step: Tensor = &y + 0.25 * &step * &k1;
309 f.forward_ts(&[x_step.squeeze(), y_step.squeeze()])?
310 };
311
312 let k3 = {
313 let x_step: Tensor = &x + 0.375 * &step;
314 let y_step: Tensor = &y + (0.09375 * &step * &k1) + (0.28125 * &step * &k2);
315 f.forward_ts(&[x_step.squeeze(), y_step.squeeze()])?
316 };
317
318 let k4 = {
319 let x_step: Tensor = &x + (12.0 / 13.0) * &step;
320 let y_step: Tensor = &y
321 + (1932.0 / 2197.0 * &step * &k1)
322 + (-7200.0 / 2197.0 * &step * &k2)
323 + (7296.0 / 2197.0 * &step * &k3);
324 f.forward_ts(&[x_step.squeeze(), y_step.squeeze()])?
325 };
326
327 let k5 = {
328 let x_step: Tensor = &x + &step;
329 let y_step: Tensor = &y
330 + (439.0 / 216.0 * &step * &k1)
331 + (-8.0 * &step * &k2)
332 + (3680.0 / 513.0 * &step * &k3)
333 + (-845.0 / 4104.0 * &step * &k4);
334 f.forward_ts(&[x_step.squeeze(), y_step.squeeze()])?
335 };
336
337 let k6 = {
338 let x_step: Tensor = &x + 0.5 * &step;
339 let y_step: Tensor = &y
340 + (-8.0 / 27.0 * &step * &k1)
341 + (2.0 * &step * &k2)
342 + (-3544.0 / 2565.0 * &step * &k3)
343 + (1859.0 / 4104.0 * &step * &k4)
344 + (-11.0 / 40.0 * &step * &k5);
345 f.forward_ts(&[x_step.squeeze(), y_step.squeeze()])?
346 };
347
348 let next_y4: Tensor = &y
349 + &step
350 * ((25.0 / 216.0 * &k1)
351 + (1408.0 / 2565.0 * &k3)
352 + (2197.0 / 4104.0 * &k4)
353 + (-1.0 / 5.0 * &k5));
354 let next_y5: Tensor = &y
355 + &step
356 * ((16.0 / 135.0 * &k1)
357 + (6656.0 / 12825.0 * &k3)
358 + (28561.0 / 56430.0 * &k4)
359 + (-9.0 / 50.0 * &k5)
360 + (2.0 / 55.0 * &k6));
361
362 let d = (&next_y4 - &next_y5).abs();
363 let e = next_y5.abs() * &rtol + &atol;
364
365 let alpha_tensor = (e / d).sqrt().min();
366 let condition = &safety_factor_tensor * &alpha_tensor;
367
368 let condition_met = condition.lt(1.0);
369 let condition_met_bool: bool = condition_met == Tensor::from(true);
370
371 if condition_met_bool {
372 step = &step * &condition;
373 if step.lt_tensor(&min_step) == Tensor::from_slice(&[true]) {
374 return Err(anyhow!("Required step is smaller than minimal step"));
375 }
376 } else {
377 y = next_y4;
378 x = &x + &step;
379 all_x.push(x.copy());
380 all_y.push(y.copy());
381
382 let new_step = &step * &condition;
383 let max_step = &step * 5.0;
384 step = new_step.fmin(&max_step);
385 }
386 }
387
388 Ok((Tensor::cat(&all_x, 0), Tensor::cat(&all_y, 0)))
389}
390
391pub fn solve_row1(
393 f: tch::CModule,
394 x_span: Tensor,
395 y0: Tensor,
396 step: Tensor,
397) -> anyhow::Result<(Tensor, Tensor)> {
398 if x_span.size() != [2] {
399 return Err(anyhow!("x_span must be of shape [2]"));
400 }
401 if y0.size().len() != 1 {
402 return Err(anyhow!("y0 must be a one-dimensional tensor"));
403 }
404 if step.size().len() != 0 {
405 return Err(anyhow!("step must be a zero-dimensional tensor"));
406 }
407
408 let x_start = x_span.i(0);
409 let x_end = x_span.i(1);
410
411 let mut x = x_start.unsqueeze(0);
412 let mut y = y0.unsqueeze(0);
413
414 let mut all_x = vec![x.copy()];
415 let mut all_y = vec![y.copy()];
416
417 while x.lt_tensor(&x_end) == Tensor::from_slice(&[true]) {
418 let remaining = &x_end - &x.squeeze();
419 let mut current_step = step.copy();
420 if remaining.lt_tensor(¤t_step) == Tensor::from(true) {
421 current_step = remaining.copy();
422 }
423
424 let x_prev = x.copy();
425 let y_prev = y.copy().squeeze();
426
427 let jacobian = compute_jacobian(
428 |y| {
429 f.forward_ts(&[x_prev.squeeze().copy(), y.copy()])
430 .unwrap()
431 .squeeze()
432 },
433 &y_prev,
434 );
435 let f_current = f
436 .forward_ts(&[x_prev.squeeze().copy(), y_prev.copy()])?
437 .squeeze();
438
439 let n = jacobian.size()[0];
440 let eye = Tensor::eye(n, (tch::Kind::Float, jacobian.device()));
441 let step_j = ¤t_step * &jacobian;
442 let inv_matrix = (eye - step_j).inverse();
443
444 let delta_y = inv_matrix.matmul(&f_current);
445 let y_next = y_prev + ¤t_step.squeeze() * delta_y;
446
447 x = &x_prev + ¤t_step;
448 y = y_next.unsqueeze(0);
449
450 all_x.push(x.copy());
451 all_y.push(y.copy());
452 }
453
454 Ok((Tensor::cat(&all_x, 0), Tensor::cat(&all_y, 0)))
455}
456
457fn compute_jacobian<F>(f: F, x: &Tensor) -> Tensor
459where
460 F: Fn(&Tensor) -> Tensor,
461{
462 assert_eq!(x.dim(), 1, "x must be 1-dimensional");
463 let mut x_with_grad = x.detach().copy().set_requires_grad(true);
464 let y = f(&x_with_grad);
465 assert_eq!(y.dim(), 1, "y must be 1-dimensional");
466
467 let y_size = y.size()[0];
468 let mut grads = Vec::new();
469
470 for i in 0..y_size {
471 let yi = y.i(i);
472 let grad = Tensor::run_backward(&[yi], &[&x_with_grad], true, false)[0].copy();
475 grads.push(grad.unsqueeze(0));
476 x_with_grad.zero_grad();
477 }
478
479 Tensor::cat(&grads, 0)
480}