diffsol/op/nonlinear_op.rs
1use super::Op;
2use crate::{Matrix, Vector};
3use num_traits::{One, Zero};
4
5// NonLinearOp is a trait that defines a nonlinear operator or function `F` that maps an input vector `x` to an output vector `y`, (i.e. `y = F(x, t)`).
6// It extends the [Op] trait with methods for computing the operator and its Jacobian.
7//
8// The operator is defined by the [Self::call_inplace] method, which computes the function `F(x, t)` at a given state and time.
9// The Jacobian is defined by the [Self::jac_mul_inplace] method, which computes the product of the Jacobian with a given vector `J(x, t) * v`.
10pub trait NonLinearOp: Op {
11 /// Compute the operator `F(x, t)` at a given state and time.
12 fn call_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::V);
13
14 /// Compute the operator `F(x, t)` at a given state and time, and return the result.
15 /// Use `[Self::call_inplace]` to for a non-allocating version.
16 fn call(&self, x: &Self::V, t: Self::T) -> Self::V {
17 let mut y = Self::V::zeros(self.nout(), self.context().clone());
18 self.call_inplace(x, t, &mut y);
19 y
20 }
21}
22
23pub trait NonLinearOpSens: NonLinearOp {
24 /// Compute the product of the gradient of F wrt a parameter vector p with a given vector `J_p(x, t) * v`.
25 /// Note that the vector v is of size nparams() and the result is of size nstates().
26 fn sens_mul_inplace(&self, _x: &Self::V, _t: Self::T, _v: &Self::V, _y: &mut Self::V);
27
28 /// Compute the product of the partial gradient of F wrt a parameter vector p with a given vector `\parial F/\partial p(x, t) * v`, and return the result.
29 /// Use `[Self::sens_mul_inplace]` to for a non-allocating version.
30 fn sens_mul(&self, x: &Self::V, t: Self::T, v: &Self::V) -> Self::V {
31 let mut y = Self::V::zeros(self.nstates(), self.context().clone());
32 self.sens_mul_inplace(x, t, v, &mut y);
33 y
34 }
35
36 /// Compute the gradient of the operator wrt a parameter vector p and store it in the matrix `y`.
37 /// `y` should have been previously initialised using the output of [Self::sens_sparsity].
38 /// The default implementation of this method computes the gradient using [Self::sens_mul_inplace],
39 /// but it can be overriden for more efficient implementations.
40 fn sens_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
41 self._default_sens_inplace(x, t, y);
42 }
43
44 /// Default implementation of the gradient computation (this is the default for [Self::sens_inplace]).
45 fn _default_sens_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
46 let mut v = Self::V::zeros(self.nparams(), self.context().clone());
47 let mut col = Self::V::zeros(self.nout(), self.context().clone());
48 for j in 0..self.nparams() {
49 v.set_index(j, Self::T::one());
50 self.sens_mul_inplace(x, t, &v, &mut col);
51 y.set_column(j, &col);
52 v.set_index(j, Self::T::zero());
53 }
54 }
55
56 /// Compute the gradient of the operator wrt a parameter vector p and return it.
57 /// See [Self::sens_inplace] for a non-allocating version.
58 fn sens(&self, x: &Self::V, t: Self::T) -> Self::M {
59 let n = self.nstates();
60 let m = self.nparams();
61 let mut y = Self::M::new_from_sparsity(n, m, self.sens_sparsity(), self.context().clone());
62 self.sens_inplace(x, t, &mut y);
63 y
64 }
65 fn sens_sparsity(&self) -> Option<<Self::M as Matrix>::Sparsity> {
66 None
67 }
68}
69pub trait NonLinearOpSensAdjoint: NonLinearOp {
70 /// Compute the product of the negative tramspose of the gradient of F wrt a parameter vector p with a given vector `-J_p(x, t)^T * v`.
71 fn sens_transpose_mul_inplace(&self, _x: &Self::V, _t: Self::T, _v: &Self::V, _y: &mut Self::V);
72
73 /// Compute the negative transpose of the gradient of the operator wrt a parameter vector p and return it.
74 /// See [Self::sens_adjoint_inplace] for a non-allocating version.
75 fn sens_adjoint(&self, x: &Self::V, t: Self::T) -> Self::M {
76 let n = self.nstates();
77 let mut y =
78 Self::M::new_from_sparsity(n, n, self.sens_adjoint_sparsity(), self.context().clone());
79 self.sens_adjoint_inplace(x, t, &mut y);
80 y
81 }
82
83 /// Compute the negative transpose of the gradient of the operator wrt a parameter vector p and store it in the matrix `y`.
84 /// `y` should have been previously initialised using the output of [Self::sens_adjoint_sparsity].
85 /// The default implementation of this method computes the gradient using [Self::sens_transpose_mul_inplace],
86 /// but it can be overriden for more efficient implementations.
87 fn sens_adjoint_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
88 self._default_sens_adjoint_inplace(x, t, y);
89 }
90
91 /// Default implementation of the gradient computation (this is the default for [Self::sens_adjoint_inplace]).
92 fn _default_sens_adjoint_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
93 let mut v = Self::V::zeros(self.nstates(), self.context().clone());
94 let mut col = Self::V::zeros(self.nout(), self.context().clone());
95 for j in 0..self.nstates() {
96 v.set_index(j, Self::T::one());
97 self.sens_transpose_mul_inplace(x, t, &v, &mut col);
98 y.set_column(j, &col);
99 v.set_index(j, Self::T::zero());
100 }
101 }
102
103 fn sens_adjoint_sparsity(&self) -> Option<<Self::M as Matrix>::Sparsity> {
104 None
105 }
106}
107pub trait NonLinearOpAdjoint: NonLinearOp {
108 /// Compute the product of the transpose of the Jacobian with a given vector `-J(x, t)^T * v`.
109 /// The default implementation fails with a panic, as this method is not implemented by default
110 /// and should be implemented by the user if needed.
111 fn jac_transpose_mul_inplace(&self, _x: &Self::V, _t: Self::T, _v: &Self::V, _y: &mut Self::V);
112
113 /// Compute the Adjoint matrix `-J^T(x, t)` of the operator and store it in the matrix `y`.
114 /// `y` should have been previously initialised using the output of [`Self::adjoint_sparsity`].
115 /// The default implementation of this method computes the Jacobian using [Self::jac_transpose_mul_inplace],
116 /// but it can be overriden for more efficient implementations.
117 fn adjoint_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
118 self._default_adjoint_inplace(x, t, y);
119 }
120
121 /// Default implementation of the Adjoint computation (this is the default for [Self::adjoint_inplace]).
122 fn _default_adjoint_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
123 let mut v = Self::V::zeros(self.nstates(), self.context().clone());
124 let mut col = Self::V::zeros(self.nout(), self.context().clone());
125 for j in 0..self.nstates() {
126 v.set_index(j, Self::T::one());
127 self.jac_transpose_mul_inplace(x, t, &v, &mut col);
128 y.set_column(j, &col);
129 v.set_index(j, Self::T::zero());
130 }
131 }
132
133 /// Compute the Adjoint matrix `-J^T(x, t)` of the operator and return it.
134 /// See [Self::adjoint_inplace] for a non-allocating version.
135 fn adjoint(&self, x: &Self::V, t: Self::T) -> Self::M {
136 let n = self.nstates();
137 let mut y =
138 Self::M::new_from_sparsity(n, n, self.adjoint_sparsity(), self.context().clone());
139 self.adjoint_inplace(x, t, &mut y);
140 y
141 }
142 /// Return sparsity information (if available)
143 fn adjoint_sparsity(&self) -> Option<<Self::M as Matrix>::Sparsity> {
144 None
145 }
146}
147pub trait NonLinearOpJacobian: NonLinearOp {
148 /// Compute the product of the Jacobian with a given vector `J(x, t) * v`.
149 fn jac_mul_inplace(&self, x: &Self::V, t: Self::T, v: &Self::V, y: &mut Self::V);
150
151 /// Compute the product of the Jacobian with a given vector `J(x, t) * v`, and return the result.
152 /// Use `[Self::jac_mul_inplace]` to for a non-allocating version.
153 fn jac_mul(&self, x: &Self::V, t: Self::T, v: &Self::V) -> Self::V {
154 let mut y = Self::V::zeros(self.nstates(), self.context().clone());
155 self.jac_mul_inplace(x, t, v, &mut y);
156 y
157 }
158
159 /// Compute the Jacobian matrix `J(x, t)` of the operator and return it.
160 /// See [Self::jacobian_inplace] for a non-allocating version.
161 fn jacobian(&self, x: &Self::V, t: Self::T) -> Self::M {
162 let n = self.nstates();
163 let mut y =
164 Self::M::new_from_sparsity(n, n, self.jacobian_sparsity(), self.context().clone());
165 self.jacobian_inplace(x, t, &mut y);
166 y
167 }
168
169 /// Return sparsity information (if available)
170 fn jacobian_sparsity(&self) -> Option<<Self::M as Matrix>::Sparsity> {
171 None
172 }
173
174 /// Compute the Jacobian matrix `J(x, t)` of the operator and store it in the matrix `y`.
175 /// `y` should have been previously initialised using the output of [Self::jacobian_sparsity].
176 /// The default implementation of this method computes the Jacobian using [Self::jac_mul_inplace],
177 /// but it can be overriden for more efficient implementations.
178 fn jacobian_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
179 self._default_jacobian_inplace(x, t, y);
180 }
181
182 /// Default implementation of the Jacobian computation (this is the default for [Self::jacobian_inplace]).
183 fn _default_jacobian_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
184 let mut v = Self::V::zeros(self.nstates(), self.context().clone());
185 let mut col = Self::V::zeros(self.nout(), self.context().clone());
186 for j in 0..self.nstates() {
187 v.set_index(j, Self::T::one());
188 self.jac_mul_inplace(x, t, &v, &mut col);
189 y.set_column(j, &col);
190 v.set_index(j, Self::T::zero());
191 }
192 }
193}