1use num_traits::{One, Signed, Zero};
2use std::{
3 cell::RefCell,
4 ops::{AddAssign, SubAssign},
5 rc::Rc,
6};
7
8use crate::{
9 error::DiffsolError, op::nonlinear_op::NonLinearOpJacobian, AugmentedOdeEquations,
10 CheckpointingPath, ConstantOp, ConstantOpSensAdjoint, LinearOp, LinearOpTranspose, Matrix,
11 NonLinearOp, NonLinearOpAdjoint, NonLinearOpSensAdjoint, OdeEquations, OdeEquationsAdjoint,
12 OdeEquationsRef, OdeSolverMethod, OdeSolverProblem, OdeSolverState, Op, Scalar, Vector,
13};
14
15pub struct AdjointContext<'a, Eqn, State, Method>
16where
17 Eqn: OdeEquations,
18 State: OdeSolverState<Eqn::V>,
19 Method: OdeSolverMethod<'a, Eqn, State = State>,
20{
21 eqn: &'a Eqn,
22 t0: Eqn::T,
23 checkpointers: CheckpointingPath<Eqn, State>,
24 active_checkpointer: usize,
25 solver: Option<RefCell<Method>>,
26 x: Eqn::V,
27 index: usize,
28 max_index: usize,
29 last_t: Option<Eqn::T>,
30 col: Eqn::V,
31}
32
33impl<'a, Eqn, State, Method> AdjointContext<'a, Eqn, State, Method>
34where
35 Eqn: OdeEquations,
36 State: OdeSolverState<Eqn::V>,
37 Method: OdeSolverMethod<'a, Eqn, State = State>,
38{
39 pub fn new(
40 eqn: &'a Eqn,
41 t0: Eqn::T,
42 checkpointers: CheckpointingPath<Eqn, State>,
43 solver: Option<Method>,
44 max_index: usize,
45 ) -> Self {
46 let active_checkpointer = checkpointers
47 .len()
48 .checked_sub(1)
49 .expect("adjoint checkpointing path must not be empty");
50 let ctx = eqn.context();
51 let x = <Eqn::V as Vector>::zeros(eqn.rhs().nstates(), ctx.clone());
52 let mut col = <Eqn::V as Vector>::zeros(max_index, ctx.clone());
53 let index = 0;
54 col.set_index(0, Eqn::T::one());
55 Self {
56 eqn,
57 t0,
58 checkpointers,
59 active_checkpointer,
60 solver: solver.map(RefCell::new),
61 x,
62 index,
63 max_index,
64 col,
65 last_t: None,
66 }
67 }
68
69 fn active_checkpointer(&self) -> &crate::Checkpointing<Eqn, State> {
70 &self.checkpointers[self.active_checkpointer]
71 }
72
73 pub(crate) fn pop_last_checkpointing(&mut self) -> Option<crate::Checkpointing<Eqn, State>> {
74 if self.active_checkpointer == self.checkpointers.len() - 1 {
75 self.active_checkpointer = self.active_checkpointer.saturating_sub(1);
76 }
77 self.checkpointers.pop()
78 }
79
80 pub fn set_state(&mut self, t: Eqn::T) {
81 if let Some(last_t) = self.last_t {
82 if last_t == t {
83 return;
84 }
85 }
86 let t0 = self.t0;
88 let t1 = self.checkpointers[self.checkpointers.len() - 1].end_t();
89 let boundary_tol = Eqn::T::EPSILON.sqrt() * (t.abs() + t1.abs() + Eqn::T::one());
90 let t_interp = if t > t1 && t - t1 <= boundary_tol {
91 t1
92 } else if t < t0 && t0 - t <= boundary_tol {
93 t0
94 } else {
95 t
96 };
97 self.last_t = Some(t_interp);
98 while self.active_checkpointer > 0
99 && t_interp + boundary_tol < self.active_checkpointer().first_t()
100 {
101 self.active_checkpointer -= 1;
102 }
103 while self.active_checkpointer + 1 < self.checkpointers.len()
104 && t_interp > self.active_checkpointer().end_t() + boundary_tol
105 {
106 self.active_checkpointer += 1;
107 }
108 let active_checkpointer = self.active_checkpointer;
109 let x = &mut self.x;
110 match self.solver.as_ref() {
111 Some(solver) => {
112 let mut solver = solver.borrow_mut();
113 self.checkpointers[active_checkpointer]
114 .interpolate(Some(&mut *solver), t_interp, x)
115 .unwrap();
116 }
117 None => self.checkpointers[active_checkpointer]
118 .interpolate::<Method>(None, t_interp, x)
119 .unwrap(),
120 }
121 self.eqn.rhs().call(&self.x, t_interp);
125 }
126
127 pub fn state(&self) -> &Eqn::V {
128 &self.x
129 }
130
131 pub fn col(&self) -> &Eqn::V {
132 &self.col
133 }
134
135 pub fn set_index(&mut self, index: usize) {
136 self.col.set_index(self.index, Eqn::T::zero());
137 self.index = index;
138 self.col.set_index(self.index, Eqn::T::one());
139 }
140}
141
142pub struct AdjointMass<'a, Eqn>
143where
144 Eqn: OdeEquations,
145{
146 eqn: &'a Eqn,
147}
148
149impl<'a, Eqn> AdjointMass<'a, Eqn>
150where
151 Eqn: OdeEquations,
152{
153 pub fn new(eqn: &'a Eqn) -> Self {
154 Self { eqn }
155 }
156}
157
158impl<Eqn> Op for AdjointMass<'_, Eqn>
159where
160 Eqn: OdeEquations,
161{
162 type T = Eqn::T;
163 type V = Eqn::V;
164 type M = Eqn::M;
165 type C = Eqn::C;
166
167 fn nstates(&self) -> usize {
168 self.eqn.rhs().nstates()
169 }
170 fn nout(&self) -> usize {
171 self.eqn.rhs().nstates()
172 }
173 fn nparams(&self) -> usize {
174 self.eqn.rhs().nparams()
175 }
176 fn context(&self) -> &Self::C {
177 self.eqn.context()
178 }
179}
180
181impl<Eqn> LinearOp for AdjointMass<'_, Eqn>
182where
183 Eqn: OdeEquationsAdjoint,
184{
185 fn gemv_inplace(&self, x: &Self::V, t: Self::T, beta: Self::T, y: &mut Self::V) {
186 self.eqn
187 .mass()
188 .unwrap()
189 .gemv_transpose_inplace(x, t, beta, y);
190 }
191
192 fn matrix_inplace(&self, t: Self::T, y: &mut Self::M) {
193 self.eqn.mass().unwrap().transpose_inplace(t, y);
194 }
195
196 fn sparsity(&self) -> Option<<Self::M as Matrix>::Sparsity> {
197 self.eqn.mass().unwrap().transpose_sparsity()
198 }
199}
200
201pub struct AdjointInit<'a, Eqn, Method>
202where
203 Eqn: OdeEquations,
204 Method: OdeSolverMethod<'a, Eqn>,
205{
206 eqn: &'a Eqn,
207 _marker: std::marker::PhantomData<Method>,
208}
209
210impl<'a, Eqn, Method> AdjointInit<'a, Eqn, Method>
211where
212 Eqn: OdeEquations,
213 Method: OdeSolverMethod<'a, Eqn>,
214{
215 pub fn new(
216 eqn: &'a Eqn,
217 _context: Rc<RefCell<AdjointContext<'a, Eqn, Method::State, Method>>>,
218 _with_out: bool,
219 ) -> Self {
220 Self {
221 eqn,
222 _marker: std::marker::PhantomData,
223 }
224 }
225}
226
227impl<'a, Eqn, Method> Op for AdjointInit<'a, Eqn, Method>
228where
229 Eqn: OdeEquations,
230 Method: OdeSolverMethod<'a, Eqn>,
231{
232 type T = Eqn::T;
233 type V = Eqn::V;
234 type M = Eqn::M;
235 type C = Eqn::C;
236
237 fn nstates(&self) -> usize {
238 self.eqn.rhs().nstates()
239 }
240 fn nout(&self) -> usize {
241 self.eqn.rhs().nstates()
242 }
243 fn nparams(&self) -> usize {
244 self.eqn.rhs().nparams()
245 }
246 fn context(&self) -> &Self::C {
247 self.eqn.context()
248 }
249}
250
251impl<'a, Eqn, Method> ConstantOp for AdjointInit<'a, Eqn, Method>
252where
253 Eqn: OdeEquations,
254 Method: OdeSolverMethod<'a, Eqn>,
255{
256 fn call_inplace(&self, _t: Self::T, y: &mut Self::V) {
257 y.fill(Eqn::T::zero());
258 }
259}
260
261pub struct AdjointRhs<'a, Eqn, Method>
270where
271 Eqn: OdeEquations,
272 Method: OdeSolverMethod<'a, Eqn>,
273{
274 eqn: &'a Eqn,
275 context: Rc<RefCell<AdjointContext<'a, Eqn, Method::State, Method>>>,
276 tmp: RefCell<Eqn::V>,
277 with_out: bool,
278}
279
280impl<'a, Eqn, Method> AdjointRhs<'a, Eqn, Method>
281where
282 Eqn: OdeEquations,
283 Method: OdeSolverMethod<'a, Eqn>,
284{
285 pub fn new(
286 eqn: &'a Eqn,
287 context: Rc<RefCell<AdjointContext<'a, Eqn, Method::State, Method>>>,
288 with_out: bool,
289 ) -> Self {
290 let tmp_n = if with_out { eqn.rhs().nstates() } else { 0 };
291 let tmp = RefCell::new(<Eqn::V as Vector>::zeros(tmp_n, eqn.context().clone()));
292 Self {
293 eqn,
294 context,
295 tmp,
296 with_out,
297 }
298 }
299}
300
301impl<'a, Eqn, Method> Op for AdjointRhs<'a, Eqn, Method>
302where
303 Eqn: OdeEquations,
304 Method: OdeSolverMethod<'a, Eqn>,
305{
306 type T = Eqn::T;
307 type V = Eqn::V;
308 type M = Eqn::M;
309 type C = Eqn::C;
310
311 fn nstates(&self) -> usize {
312 self.eqn.rhs().nstates()
313 }
314 fn nout(&self) -> usize {
315 self.eqn.rhs().nstates()
316 }
317 fn nparams(&self) -> usize {
318 self.eqn.rhs().nparams()
319 }
320 fn context(&self) -> &Self::C {
321 self.eqn.context()
322 }
323}
324
325impl<'a, Eqn, Method> NonLinearOp for AdjointRhs<'a, Eqn, Method>
326where
327 Eqn: OdeEquationsAdjoint,
328 Method: OdeSolverMethod<'a, Eqn>,
329{
330 fn call_inplace(&self, lambda: &Self::V, t: Self::T, y: &mut Self::V) {
332 self.context.borrow_mut().set_state(t);
333 let context = self.context.borrow();
334 let x = context.state();
335
336 self.eqn.rhs().jac_transpose_mul_inplace(x, t, lambda, y);
338
339 if self.with_out {
341 let col = context.col();
342 if let Some(out) = self.eqn.out() {
343 let mut tmp = self.tmp.borrow_mut();
344 out.jac_transpose_mul_inplace(x, t, col, &mut tmp);
345 y.add_assign(&*tmp);
346 } else {
347 y.add_assign(col);
349 }
350 }
351 }
352}
353
354impl<'a, Eqn, Method> NonLinearOpJacobian for AdjointRhs<'a, Eqn, Method>
355where
356 Eqn: OdeEquationsAdjoint,
357 Method: OdeSolverMethod<'a, Eqn>,
358{
359 fn jac_mul_inplace(&self, _x: &Self::V, t: Self::T, v: &Self::V, y: &mut Self::V) {
361 self.context.borrow_mut().set_state(t);
362 let context = self.context.borrow();
363 let x = context.state();
364 self.eqn.rhs().jac_transpose_mul_inplace(x, t, v, y);
365 }
366 fn jacobian_inplace(&self, _x: &Self::V, t: Self::T, y: &mut Self::M) {
367 self.context.borrow_mut().set_state(t);
368 let context = self.context.borrow();
369 let x = context.state();
370 self.eqn.rhs().adjoint_inplace(x, t, y);
371 }
372 fn jacobian_sparsity(&self) -> Option<<Self::M as Matrix>::Sparsity> {
373 self.eqn.rhs().adjoint_sparsity()
374 }
375}
376
377pub struct AdjointOut<'a, Eqn, Method>
386where
387 Eqn: OdeEquations,
388 Method: OdeSolverMethod<'a, Eqn>,
389{
390 eqn: &'a Eqn,
391 context: Rc<RefCell<AdjointContext<'a, Eqn, Method::State, Method>>>,
392 tmp: RefCell<Eqn::V>,
393 with_out: bool,
394}
395
396impl<'a, Eqn, Method> AdjointOut<'a, Eqn, Method>
397where
398 Eqn: OdeEquations,
399 Method: OdeSolverMethod<'a, Eqn>,
400{
401 pub fn new(
402 eqn: &'a Eqn,
403 context: Rc<RefCell<AdjointContext<'a, Eqn, Method::State, Method>>>,
404 with_out: bool,
405 ) -> Self {
406 let tmp_n = if with_out { eqn.rhs().nparams() } else { 0 };
407 let tmp = RefCell::new(<Eqn::V as Vector>::zeros(tmp_n, eqn.context().clone()));
408 Self {
409 eqn,
410 context,
411 tmp,
412 with_out,
413 }
414 }
415}
416
417impl<'a, Eqn, Method> Op for AdjointOut<'a, Eqn, Method>
418where
419 Eqn: OdeEquations,
420 Method: OdeSolverMethod<'a, Eqn>,
421{
422 type T = Eqn::T;
423 type V = Eqn::V;
424 type M = Eqn::M;
425 type C = Eqn::C;
426
427 fn nstates(&self) -> usize {
428 self.eqn.rhs().nstates()
429 }
430 fn nout(&self) -> usize {
431 self.eqn.rhs().nparams()
432 }
433 fn nparams(&self) -> usize {
434 self.eqn.rhs().nparams()
435 }
436 fn context(&self) -> &Self::C {
437 self.eqn.context()
438 }
439}
440
441impl<'a, Eqn, Method> NonLinearOp for AdjointOut<'a, Eqn, Method>
442where
443 Eqn: OdeEquationsAdjoint,
444 Method: OdeSolverMethod<'a, Eqn>,
445{
446 fn call_inplace(&self, lambda: &Self::V, t: Self::T, y: &mut Self::V) {
448 self.context.borrow_mut().set_state(t);
449 let context = self.context.borrow();
450 let x = context.state();
451 self.eqn.rhs().sens_transpose_mul_inplace(x, t, lambda, y);
452
453 if self.with_out {
454 let col = context.col();
455 if let Some(out) = self.eqn.out() {
456 let mut tmp = self.tmp.borrow_mut();
457 out.sens_transpose_mul_inplace(x, t, col, &mut tmp);
458 y.add_assign(&*tmp);
459 }
460 }
461 }
462}
463
464impl<'a, Eqn, Method> NonLinearOpJacobian for AdjointOut<'a, Eqn, Method>
465where
466 Eqn: OdeEquationsAdjoint,
467 Method: OdeSolverMethod<'a, Eqn>,
468{
469 fn jac_mul_inplace(&self, _x: &Self::V, t: Self::T, v: &Self::V, y: &mut Self::V) {
471 self.context.borrow_mut().set_state(t);
472 let context = self.context.borrow();
473 let x = context.state();
474 self.eqn.rhs().sens_transpose_mul_inplace(x, t, v, y);
475 }
476 fn jacobian_inplace(&self, _x: &Self::V, t: Self::T, y: &mut Self::M) {
477 self.context.borrow_mut().set_state(t);
478 let context = self.context.borrow();
479 let x = context.state();
480 self.eqn.rhs().sens_adjoint_inplace(x, t, y);
481 }
482 fn jacobian_sparsity(&self) -> Option<<Self::M as Matrix>::Sparsity> {
483 self.eqn.rhs().sens_adjoint_sparsity()
484 }
485}
486
487pub struct AdjointEquations<'a, Eqn, Method>
494where
495 Eqn: OdeEquations,
496 Method: OdeSolverMethod<'a, Eqn>,
497{
498 eqn: &'a Eqn,
499 rhs: AdjointRhs<'a, Eqn, Method>,
500 out: AdjointOut<'a, Eqn, Method>,
501 mass: Option<AdjointMass<'a, Eqn>>,
502 context: Rc<RefCell<AdjointContext<'a, Eqn, Method::State, Method>>>,
503 tmp: RefCell<Eqn::V>,
504 tmp2: RefCell<Eqn::V>,
505 init: AdjointInit<'a, Eqn, Method>,
506 atol: Option<&'a Eqn::V>,
507 rtol: Option<Eqn::T>,
508 out_rtol: Option<Eqn::T>,
509 out_atol: Option<&'a Eqn::V>,
510}
511
512impl<'a, Eqn, Method> Clone for AdjointEquations<'a, Eqn, Method>
513where
514 Eqn: OdeEquations,
515 Method: OdeSolverMethod<'a, Eqn>,
516{
517 fn clone(&self) -> Self {
518 let context_ref = self.context.borrow();
519 let context = Rc::new(RefCell::new(AdjointContext::new(
520 context_ref.eqn,
521 context_ref.t0,
522 context_ref.checkpointers.clone(),
523 context_ref
524 .solver
525 .as_ref()
526 .map(|solver| solver.borrow().clone()),
527 context_ref.max_index,
528 )));
529 let rhs = AdjointRhs::new(self.eqn, context.clone(), self.rhs.with_out);
530 let init = AdjointInit::new(self.eqn, context.clone(), self.rhs.with_out);
531 let out = AdjointOut::new(self.eqn, context.clone(), self.out.with_out);
532 let tmp = self.tmp.clone();
533 let tmp2 = self.tmp2.clone();
534 let atol = self.atol;
535 let rtol = self.rtol;
536 let out_atol = self.out_atol;
537 let out_rtol = self.out_rtol;
538 let mass = self.eqn.mass().map(|_m| AdjointMass::new(self.eqn));
539 Self {
540 rhs,
541 init,
542 mass,
543 context,
544 out,
545 tmp,
546 tmp2,
547 eqn: self.eqn,
548 atol,
549 rtol,
550 out_rtol,
551 out_atol,
552 }
553 }
554}
555
556impl<'a, Eqn, Method> AdjointEquations<'a, Eqn, Method>
557where
558 Eqn: OdeEquationsAdjoint,
559 Method: OdeSolverMethod<'a, Eqn>,
560{
561 pub(crate) fn new(
562 problem: &'a OdeSolverProblem<Eqn>,
563 context: Rc<RefCell<AdjointContext<'a, Eqn, Method::State, Method>>>,
564 with_out: bool,
565 ) -> Self {
566 let eqn = &problem.eqn;
567 let rhs = AdjointRhs::new(eqn, context.clone(), with_out);
568 let init = AdjointInit::new(eqn, context.clone(), with_out);
569 let out = AdjointOut::new(eqn, context.clone(), with_out);
570 let tmp = RefCell::new(<Eqn::V as Vector>::zeros(
571 eqn.rhs().nparams(),
572 eqn.context().clone(),
573 ));
574 let tmp2 = RefCell::new(<Eqn::V as Vector>::zeros(
575 eqn.rhs().nstates(),
576 eqn.context().clone(),
577 ));
578 let atol = problem.sens_atol.as_ref().or(Some(&problem.atol));
579 let rtol = problem.sens_rtol.or(Some(problem.rtol));
580 let out_atol = problem.param_atol.as_ref();
581 let out_rtol = problem.param_rtol;
582 let mass = eqn.mass().map(|_m| AdjointMass::new(eqn));
583 Self {
584 rhs,
585 init,
586 mass,
587 context,
588 out,
589 tmp,
590 tmp2,
591 eqn,
592 atol,
593 rtol,
594 out_rtol,
595 out_atol,
596 }
597 }
598
599 pub fn eqn(&self) -> &'a Eqn {
600 self.eqn
601 }
602
603 pub fn last_t(&self) -> Eqn::T {
604 self.context.borrow().checkpointers.last().unwrap().last_t()
605 }
606
607 pub fn last_h(&self) -> Option<Eqn::T> {
608 self.context.borrow().checkpointers.last().unwrap().last_h()
609 }
610
611 pub(crate) fn checkpointing_len(&self) -> usize {
612 self.context.borrow().checkpointers.len()
613 }
614
615 pub(crate) fn checkpointing_bounds(&self, index: usize) -> (Eqn::T, Eqn::T) {
616 let context = self.context.borrow();
617 let checkpointer = &context.checkpointers[index];
618 (checkpointer.first_t(), checkpointer.end_t())
619 }
620
621 pub(crate) fn checkpointing_terminal_reset_root_idx(&self, index: usize) -> Option<usize> {
622 self.context.borrow().checkpointers[index].terminal_reset_root_idx()
623 }
624
625 pub fn with_out(&self) -> bool {
626 self.rhs.with_out
627 }
628
629 pub fn correct_sg_for_init(&self, t: Eqn::T, s: &[Eqn::V], sg: &mut [Eqn::V]) {
630 let mut tmp = self.tmp.borrow_mut();
631 for (s_i, sg_i) in s.iter().zip(sg.iter_mut()) {
632 if let Some(mass) = self.eqn.mass() {
633 let mut tmp2 = self.tmp2.borrow_mut();
634 mass.call_transpose_inplace(s_i, t, &mut tmp2);
635 self.eqn
636 .init()
637 .sens_transpose_mul_inplace(t, &tmp2, &mut tmp);
638 sg_i.sub_assign(&*tmp);
639 } else {
640 self.eqn.init().sens_transpose_mul_inplace(t, s_i, &mut tmp);
641 sg_i.sub_assign(&*tmp);
642 }
643 }
644 }
645
646 pub fn interpolate_forward_state(&self, t: Eqn::T, y: &mut Eqn::V) -> Result<(), DiffsolError> {
647 let mut context = self.context.borrow_mut();
648 context.set_state(t);
649 y.copy_from(context.state());
650 Ok(())
651 }
652
653 pub fn checkpointing_last_state(&self, index: usize) -> Method::State {
654 self.context.borrow().checkpointers[index]
655 .last_checkpoint()
656 .clone()
657 }
658
659 pub fn checkpointing_first_state(&self, index: usize) -> Method::State {
660 self.context.borrow().checkpointers[index]
661 .first_checkpoint()
662 .clone()
663 }
664
665 pub fn pop_last_checkpointing(
666 &mut self,
667 ) -> Result<crate::Checkpointing<Eqn, Method::State>, DiffsolError> {
668 let mut context = self.context.borrow_mut();
669 context
670 .pop_last_checkpointing()
671 .ok_or_else(|| DiffsolError::Other("No more checkpointing to pop".to_string()))
672 }
673
674 pub fn into_checkpointing(self) -> CheckpointingPath<Eqn, Method::State> {
675 let Self {
676 rhs,
677 out,
678 context,
679 eqn: _,
680 mass: _,
681 tmp: _,
682 tmp2: _,
683 init: _,
684 atol: _,
685 rtol: _,
686 out_rtol: _,
687 out_atol: _,
688 } = self;
689
690 drop(rhs);
691 drop(out);
692
693 match Rc::try_unwrap(context) {
694 Ok(context) => context.into_inner().checkpointers,
695 Err(_) => {
696 panic!("adjoint context should be uniquely owned after consuming AdjointEquations")
697 }
698 }
699 }
700}
701
702impl<'a, Eqn, Method> std::fmt::Debug for AdjointEquations<'a, Eqn, Method>
703where
704 Eqn: OdeEquations,
705 Method: OdeSolverMethod<'a, Eqn>,
706{
707 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
708 f.debug_struct("AdjointEquations").finish()
709 }
710}
711
712impl<'a, Eqn, Method> Op for AdjointEquations<'a, Eqn, Method>
713where
714 Eqn: OdeEquations,
715 Method: OdeSolverMethod<'a, Eqn>,
716{
717 type T = Eqn::T;
718 type V = Eqn::V;
719 type M = Eqn::M;
720 type C = Eqn::C;
721
722 fn nstates(&self) -> usize {
723 self.eqn.rhs().nstates()
724 }
725 fn nout(&self) -> usize {
726 self.eqn.rhs().nout()
727 }
728 fn nparams(&self) -> usize {
729 self.eqn.rhs().nparams()
730 }
731 fn context(&self) -> &Self::C {
732 self.eqn.context()
733 }
734}
735
736impl<'a, 'b, Eqn, Method> OdeEquationsRef<'a> for AdjointEquations<'b, Eqn, Method>
737where
738 Eqn: OdeEquationsAdjoint,
739 Method: OdeSolverMethod<'b, Eqn>,
740{
741 type Rhs = &'a AdjointRhs<'b, Eqn, Method>;
742 type Mass = &'a AdjointMass<'b, Eqn>;
743 type Root = <Eqn as OdeEquationsRef<'a>>::Root;
744 type Init = &'a AdjointInit<'b, Eqn, Method>;
745 type Out = &'a AdjointOut<'b, Eqn, Method>;
746 type Reset = <Eqn as OdeEquationsRef<'a>>::Reset;
747}
748
749impl<'a, Eqn, Method> OdeEquations for AdjointEquations<'a, Eqn, Method>
750where
751 Eqn: OdeEquationsAdjoint,
752 Method: OdeSolverMethod<'a, Eqn>,
753{
754 fn rhs(&self) -> &AdjointRhs<'a, Eqn, Method> {
755 &self.rhs
756 }
757 fn mass(&self) -> Option<&AdjointMass<'a, Eqn>> {
758 self.mass.as_ref()
759 }
760 fn root(&self) -> Option<<Eqn as OdeEquationsRef<'_>>::Root> {
761 None
762 }
763 fn init(&self) -> &AdjointInit<'a, Eqn, Method> {
764 &self.init
765 }
766 fn out(&self) -> Option<&AdjointOut<'a, Eqn, Method>> {
767 Some(&self.out)
768 }
769 fn set_params(&mut self, p: &Self::V) {
770 self.eqn.set_params(p);
771 }
772 fn set_model_index(&mut self, m: usize) {
773 self.eqn.set_model_index(m);
774 }
775 fn get_params(&self, p: &mut Self::V) {
776 self.eqn.get_params(p);
777 }
778}
779
780impl<'a, Eqn, Method> AugmentedOdeEquations<Eqn> for AdjointEquations<'a, Eqn, Method>
781where
782 Eqn: OdeEquationsAdjoint,
783 Method: OdeSolverMethod<'a, Eqn>,
784{
785 fn include_in_error_control(&self) -> bool {
786 self.atol.is_some() && self.rtol.is_some()
787 }
788 fn include_out_in_error_control(&self) -> bool {
789 self.out().is_some() && self.out_atol.is_some() && self.out_rtol.is_some()
790 }
791
792 fn atol(&self) -> Option<&Eqn::V> {
793 self.atol
794 }
795 fn out_atol(&self) -> Option<&Eqn::V> {
796 self.out_atol
797 }
798 fn out_rtol(&self) -> Option<Eqn::T> {
799 self.out_rtol
800 }
801 fn rtol(&self) -> Option<Eqn::T> {
802 self.rtol
803 }
804
805 fn max_index(&self) -> usize {
806 self.context.borrow().max_index
807 }
808
809 fn set_index(&mut self, index: usize) {
810 self.context.borrow_mut().set_index(index);
811 }
812
813 fn update_rhs_out_state(&mut self, _y: &Eqn::V, _dy: &Eqn::V, _t: Eqn::T) {}
814
815 fn integrate_main_eqn(&self) -> bool {
816 false
817 }
818}
819
820#[cfg(test)]
821mod tests {
822 use std::{cell::RefCell, rc::Rc};
823
824 use crate::{
825 matrix::dense_nalgebra_serial::NalgebraMat,
826 ode_equations::{
827 adjoint_equations::AdjointEquations,
828 test_models::exponential_decay::exponential_decay_problem_adjoint,
829 },
830 AdjointContext, AugmentedOdeEquations, Checkpointing, DenseMatrix, FaerSparseLU,
831 FaerSparseMat, FaerVec, Matrix, MatrixCommon, NalgebraVec, NonLinearOp,
832 NonLinearOpJacobian, OdeEquations, Op, RkState, Vector,
833 };
834 type Mcpu = NalgebraMat<f64>;
835 type Vcpu = NalgebraVec<f64>;
836 type LS = crate::NalgebraLU<f64>;
837
838 #[test]
839 fn test_rhs_exponential() {
840 let (problem, _soln) = exponential_decay_problem_adjoint::<Mcpu>(true, true);
843 let ctx = problem.eqn.context();
844 let state = RkState {
845 t: 0.0,
846 y: Vcpu::from_vec(vec![1.0, 1.0], *ctx),
847 dy: Vcpu::from_vec(vec![1.0, 1.0], *ctx),
848 g: Vcpu::zeros(0, *ctx),
849 dg: Vcpu::zeros(0, *ctx),
850 sg: Vec::new(),
851 dsg: Vec::new(),
852 s: Vec::new(),
853 ds: Vec::new(),
854 h: 0.0,
855 };
856 let nout = problem.eqn.out().unwrap().nout();
857 let mut solver = problem.esdirk34_solver::<LS>(state.clone()).unwrap();
858 let checkpointer = Checkpointing::new(
859 Some(&mut solver),
860 0,
861 vec![state.clone(), state.clone()],
862 None,
863 );
864 let context = Rc::new(RefCell::new(AdjointContext::new(
865 &problem.eqn,
866 problem.t0,
867 vec![checkpointer],
868 Some(solver.clone()),
869 nout,
870 )));
871 let adj_eqn = AdjointEquations::new(&problem, context.clone(), false);
872 let v = Vcpu::from_vec(vec![1.0, 2.0], *ctx);
878 let f = adj_eqn.rhs.call(&v, state.t);
879 let f_expect = Vcpu::from_vec(vec![0.1, 0.2], *ctx);
880 f.assert_eq_st(&f_expect, 1e-10);
881
882 let mut adj_eqn = AdjointEquations::new(&problem, context, true);
883
884 let adjoint = adj_eqn.rhs.jacobian(&state.y, state.t);
888 assert_eq!(adjoint.nrows(), 2);
889 assert_eq!(adjoint.ncols(), 2);
890 assert_eq!(adjoint.get_index(0, 0), 0.1);
891 assert_eq!(adjoint.get_index(1, 1), 0.1);
892
893 adj_eqn.set_index(0);
907 let out = adj_eqn.out.call(&v, state.t);
908 let out_expect = Vcpu::from_vec(vec![3.0, 0.0], *ctx);
909 out.assert_eq_st(&out_expect, 1e-10);
910
911 let f = adj_eqn.rhs.call(&v, state.t);
917 let f_expect = Vcpu::from_vec(vec![-0.9, -1.8], *ctx);
918 f.assert_eq_st(&f_expect, 1e-10);
919 }
920
921 #[test]
922 fn test_rhs_exponential_sparse() {
923 let (problem, _soln) = exponential_decay_problem_adjoint::<FaerSparseMat<f64>>(true, true);
926 let ctx = problem.eqn.context();
927 let state = RkState {
928 t: 0.0,
929 y: FaerVec::from_vec(vec![1.0, 1.0], *ctx),
930 dy: FaerVec::from_vec(vec![1.0, 1.0], *ctx),
931 g: FaerVec::zeros(0, *ctx),
932 dg: FaerVec::zeros(0, *ctx),
933 sg: Vec::new(),
934 dsg: Vec::new(),
935 s: Vec::new(),
936 ds: Vec::new(),
937 h: 0.0,
938 };
939 let nout = problem.eqn.out().unwrap().nout();
940 let mut solver = problem
941 .esdirk34_solver::<FaerSparseLU<f64>>(state.clone())
942 .unwrap();
943 let checkpointer = Checkpointing::new(
944 Some(&mut solver),
945 0,
946 vec![state.clone(), state.clone()],
947 None,
948 );
949 let context = Rc::new(RefCell::new(AdjointContext::new(
950 &problem.eqn,
951 problem.t0,
952 vec![checkpointer],
953 Some(solver.clone()),
954 nout,
955 )));
956 let mut adj_eqn = AdjointEquations::new(&problem, context, true);
957
958 let adjoint = adj_eqn.rhs.jacobian(&state.y, state.t);
962 assert_eq!(adjoint.nrows(), 2);
963 assert_eq!(adjoint.ncols(), 2);
964 let (idx, vals) = adjoint.triplet_iter();
965 for ((i, j), v) in idx.zip(vals) {
966 if i == j {
967 assert_eq!(v, 0.1);
968 } else {
969 assert_eq!(v, 0.0);
970 }
971 }
972
973 adj_eqn.set_index(0);
985 let v = FaerVec::from_vec(vec![1.0, 2.0], *ctx);
986 let f = adj_eqn.rhs.call(&v, state.t);
987 let f_expect = FaerVec::from_vec(vec![-0.9, -1.8], *ctx);
988 f.assert_eq_st(&f_expect, 1e-10);
989 }
990}