use super::Op;
use crate::{scale, Matrix, Scalar, Vector};
use num_traits::{One, Signed, Zero};
pub trait NonLinearOp: Op {
fn call_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::V);
fn call(&self, x: &Self::V, t: Self::T) -> Self::V {
let mut y = Self::V::zeros(self.nout(), self.context().clone());
self.call_inplace(x, t, &mut y);
y
}
}
pub trait NonLinearOpTimePartial: NonLinearOp {
fn time_derive_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::V) {
let eps_sqrt = Self::T::EPSILON.sqrt();
let h = (Self::T::one() + t.abs()) * eps_sqrt;
let mut y_plus = Self::V::zeros(self.nout(), self.context().clone());
let mut y_minus = Self::V::zeros(self.nout(), self.context().clone());
self.call_inplace(x, t + h, &mut y_plus);
self.call_inplace(x, t - h, &mut y_minus);
y.copy_from(&y_plus);
*y -= &y_minus;
*y *= scale(Self::T::one() / (h + h));
}
fn time_derive(&self, x: &Self::V, t: Self::T) -> Self::V {
let mut y = Self::V::zeros(self.nout(), self.context().clone());
self.time_derive_inplace(x, t, &mut y);
y
}
}
impl<T: NonLinearOp> NonLinearOpTimePartial for T {}
pub trait NonLinearOpSens: NonLinearOp {
fn sens_mul_inplace(&self, _x: &Self::V, _t: Self::T, _v: &Self::V, _y: &mut Self::V);
fn sens_mul(&self, x: &Self::V, t: Self::T, v: &Self::V) -> Self::V {
let mut y = Self::V::zeros(self.nstates(), self.context().clone());
self.sens_mul_inplace(x, t, v, &mut y);
y
}
fn sens_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
self._default_sens_inplace(x, t, y);
}
fn _default_sens_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
let mut v = Self::V::zeros(self.nparams(), self.context().clone());
let mut col = Self::V::zeros(self.nout(), self.context().clone());
for j in 0..self.nparams() {
v.set_index(j, Self::T::one());
self.sens_mul_inplace(x, t, &v, &mut col);
y.set_column(j, &col);
v.set_index(j, Self::T::zero());
}
}
fn sens(&self, x: &Self::V, t: Self::T) -> Self::M {
let n = self.nstates();
let m = self.nparams();
let mut y = Self::M::new_from_sparsity(n, m, self.sens_sparsity(), self.context().clone());
self.sens_inplace(x, t, &mut y);
y
}
fn sens_sparsity(&self) -> Option<<Self::M as Matrix>::Sparsity> {
None
}
}
pub trait NonLinearOpSensAdjoint: NonLinearOp {
fn sens_transpose_mul_inplace(&self, _x: &Self::V, _t: Self::T, _v: &Self::V, _y: &mut Self::V);
fn sens_adjoint(&self, x: &Self::V, t: Self::T) -> Self::M {
let n = self.nstates();
let mut y =
Self::M::new_from_sparsity(n, n, self.sens_adjoint_sparsity(), self.context().clone());
self.sens_adjoint_inplace(x, t, &mut y);
y
}
fn sens_adjoint_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
self._default_sens_adjoint_inplace(x, t, y);
}
fn _default_sens_adjoint_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
let mut v = Self::V::zeros(self.nstates(), self.context().clone());
let mut col = Self::V::zeros(self.nout(), self.context().clone());
for j in 0..self.nstates() {
v.set_index(j, Self::T::one());
self.sens_transpose_mul_inplace(x, t, &v, &mut col);
y.set_column(j, &col);
v.set_index(j, Self::T::zero());
}
}
fn sens_adjoint_sparsity(&self) -> Option<<Self::M as Matrix>::Sparsity> {
None
}
}
pub trait NonLinearOpAdjoint: NonLinearOp {
fn jac_transpose_mul_inplace(&self, _x: &Self::V, _t: Self::T, _v: &Self::V, _y: &mut Self::V);
fn adjoint_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
self._default_adjoint_inplace(x, t, y);
}
fn _default_adjoint_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
let mut v = Self::V::zeros(self.nstates(), self.context().clone());
let mut col = Self::V::zeros(self.nout(), self.context().clone());
for j in 0..self.nstates() {
v.set_index(j, Self::T::one());
self.jac_transpose_mul_inplace(x, t, &v, &mut col);
y.set_column(j, &col);
v.set_index(j, Self::T::zero());
}
}
fn adjoint(&self, x: &Self::V, t: Self::T) -> Self::M {
let n = self.nstates();
let mut y =
Self::M::new_from_sparsity(n, n, self.adjoint_sparsity(), self.context().clone());
self.adjoint_inplace(x, t, &mut y);
y
}
fn adjoint_sparsity(&self) -> Option<<Self::M as Matrix>::Sparsity> {
None
}
}
pub trait NonLinearOpJacobian: NonLinearOp {
fn jac_mul_inplace(&self, x: &Self::V, t: Self::T, v: &Self::V, y: &mut Self::V);
fn jac_mul(&self, x: &Self::V, t: Self::T, v: &Self::V) -> Self::V {
let mut y = Self::V::zeros(self.nstates(), self.context().clone());
self.jac_mul_inplace(x, t, v, &mut y);
y
}
fn jacobian(&self, x: &Self::V, t: Self::T) -> Self::M {
let n = self.nstates();
let mut y =
Self::M::new_from_sparsity(n, n, self.jacobian_sparsity(), self.context().clone());
self.jacobian_inplace(x, t, &mut y);
y
}
fn jacobian_sparsity(&self) -> Option<<Self::M as Matrix>::Sparsity> {
None
}
fn jacobian_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
self._default_jacobian_inplace(x, t, y);
}
fn _default_jacobian_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::M) {
let mut v = Self::V::zeros(self.nstates(), self.context().clone());
let mut col = Self::V::zeros(self.nout(), self.context().clone());
for j in 0..self.nstates() {
v.set_index(j, Self::T::one());
self.jac_mul_inplace(x, t, &v, &mut col);
y.set_column(j, &col);
v.set_index(j, Self::T::zero());
}
}
}
#[cfg(test)]
mod tests {
use crate::{
context::nalgebra::NalgebraContext, matrix::dense_nalgebra_serial::NalgebraMat,
DenseMatrix, NonLinearOp, NonLinearOpAdjoint, NonLinearOpJacobian, NonLinearOpSens,
NonLinearOpSensAdjoint, NonLinearOpTimePartial, Op, Vector,
};
type M = NalgebraMat<f64>;
struct FakeNonLinearOp {
ctx: NalgebraContext,
}
struct TimeDependentFakeNonLinearOp {
ctx: NalgebraContext,
}
impl Op for FakeNonLinearOp {
type T = f64;
type V = crate::NalgebraVec<f64>;
type M = M;
type C = NalgebraContext;
fn context(&self) -> &Self::C {
&self.ctx
}
fn nstates(&self) -> usize {
2
}
fn nout(&self) -> usize {
2
}
fn nparams(&self) -> usize {
2
}
}
impl NonLinearOp for FakeNonLinearOp {
fn call_inplace(&self, x: &Self::V, _t: Self::T, y: &mut Self::V) {
y.copy_from(&Self::V::from_vec(
vec![
2.0 * x.get_index(0) + 3.0 * x.get_index(1),
-x.get_index(0) + 4.0 * x.get_index(1),
],
NalgebraContext,
));
}
}
impl Op for TimeDependentFakeNonLinearOp {
type T = f64;
type V = crate::NalgebraVec<f64>;
type M = M;
type C = NalgebraContext;
fn context(&self) -> &Self::C {
&self.ctx
}
fn nstates(&self) -> usize {
2
}
fn nout(&self) -> usize {
2
}
fn nparams(&self) -> usize {
0
}
}
impl NonLinearOp for TimeDependentFakeNonLinearOp {
fn call_inplace(&self, x: &Self::V, t: Self::T, y: &mut Self::V) {
y.copy_from(&Self::V::from_vec(
vec![
2.0 * x.get_index(0) + t,
-x.get_index(0) + 4.0 * x.get_index(1) - 3.0 * t,
],
NalgebraContext,
));
}
}
impl NonLinearOpJacobian for FakeNonLinearOp {
fn jac_mul_inplace(&self, _x: &Self::V, _t: Self::T, v: &Self::V, y: &mut Self::V) {
y.copy_from(&Self::V::from_vec(
vec![
2.0 * v.get_index(0) + 3.0 * v.get_index(1),
-v.get_index(0) + 4.0 * v.get_index(1),
],
NalgebraContext,
));
}
}
impl NonLinearOpAdjoint for FakeNonLinearOp {
fn jac_transpose_mul_inplace(
&self,
_x: &Self::V,
_t: Self::T,
v: &Self::V,
y: &mut Self::V,
) {
y.copy_from(&Self::V::from_vec(
vec![
-2.0 * v.get_index(0) + v.get_index(1),
-3.0 * v.get_index(0) - 4.0 * v.get_index(1),
],
NalgebraContext,
));
}
}
impl NonLinearOpSens for FakeNonLinearOp {
fn sens_mul_inplace(&self, _x: &Self::V, _t: Self::T, v: &Self::V, y: &mut Self::V) {
y.copy_from(&Self::V::from_vec(
vec![
v.get_index(0) + 2.0 * v.get_index(1),
3.0 * v.get_index(0) + 4.0 * v.get_index(1),
],
NalgebraContext,
));
}
}
impl NonLinearOpSensAdjoint for FakeNonLinearOp {
fn sens_transpose_mul_inplace(
&self,
_x: &Self::V,
_t: Self::T,
v: &Self::V,
y: &mut Self::V,
) {
y.copy_from(&Self::V::from_vec(
vec![
-v.get_index(0) - 3.0 * v.get_index(1),
-2.0 * v.get_index(0) - 4.0 * v.get_index(1),
],
NalgebraContext,
));
}
}
#[test]
fn nonlinear_op_default_helpers_construct_expected_vectors_and_matrices() {
let op = FakeNonLinearOp {
ctx: NalgebraContext,
};
let x = crate::NalgebraVec::from_vec(vec![1.0, 2.0], NalgebraContext);
let v = crate::NalgebraVec::from_vec(vec![3.0, -1.0], NalgebraContext);
op.call(&x, 0.0).assert_eq_st(
&crate::NalgebraVec::from_vec(vec![8.0, 7.0], NalgebraContext),
1e-12,
);
op.jac_mul(&x, 0.0, &v).assert_eq_st(
&crate::NalgebraVec::from_vec(vec![3.0, -7.0], NalgebraContext),
1e-12,
);
op.sens_mul(&x, 0.0, &v).assert_eq_st(
&crate::NalgebraVec::from_vec(vec![1.0, 5.0], NalgebraContext),
1e-12,
);
let jac = op.jacobian(&x, 0.0);
assert_eq!(jac.get_index(0, 0), 2.0);
assert_eq!(jac.get_index(1, 0), -1.0);
assert_eq!(jac.get_index(0, 1), 3.0);
assert_eq!(jac.get_index(1, 1), 4.0);
let adj = op.adjoint(&x, 0.0);
assert_eq!(adj.get_index(0, 0), -2.0);
assert_eq!(adj.get_index(1, 0), -3.0);
assert_eq!(adj.get_index(0, 1), 1.0);
assert_eq!(adj.get_index(1, 1), -4.0);
let sens = op.sens(&x, 0.0);
assert_eq!(sens.get_index(0, 0), 1.0);
assert_eq!(sens.get_index(1, 0), 3.0);
assert_eq!(sens.get_index(0, 1), 2.0);
assert_eq!(sens.get_index(1, 1), 4.0);
let sens_adj = op.sens_adjoint(&x, 0.0);
assert_eq!(sens_adj.get_index(0, 0), -1.0);
assert_eq!(sens_adj.get_index(1, 0), -2.0);
assert_eq!(sens_adj.get_index(0, 1), -3.0);
assert_eq!(sens_adj.get_index(1, 1), -4.0);
}
#[test]
fn nonlinear_op_time_partial_default_helper_uses_finite_differences() {
let op = TimeDependentFakeNonLinearOp {
ctx: NalgebraContext,
};
let x = crate::NalgebraVec::from_vec(vec![1.0, 2.0], NalgebraContext);
op.time_derive(&x, 0.5).assert_eq_st(
&crate::NalgebraVec::from_vec(vec![1.0, -3.0], NalgebraContext),
1e-8,
);
let mut y = crate::NalgebraVec::zeros(2, NalgebraContext);
op.time_derive_inplace(&x, 0.5, &mut y);
y.assert_eq_st(
&crate::NalgebraVec::from_vec(vec![1.0, -3.0], NalgebraContext),
1e-8,
);
}
}