#[inline(always)]
pub fn hess_index(i: usize, j: usize) -> Option<usize> {
if i < j {
hess_index(j, i)
} else {
Some(i * (i + 1) / 2 + j)
}
}
#[inline(always)]
pub fn hess_idx(i: usize, j: usize) -> usize {
i * (i + 1) / 2 + j
}
#[inline(always)]
pub const fn hess_size(n: usize) -> usize {
n * (n + 1) / 2
}
#[derive(Clone, Copy)]
pub struct Jet1<const N: usize> {
pub value: f64,
pub grad: [f64; N],
}
#[derive(Clone, Copy)]
pub struct Jet2<const N: usize, const H: usize> {
pub value: f64,
pub grad: [f64; N],
pub hess: [f64; H],
}
impl<const N: usize> Jet1<N> {
pub fn constant(value: f64) -> Self {
Self {
value,
grad: [0.0; N],
}
}
pub fn variable(value: f64, param_idx: usize) -> Self {
let mut jet = Self::constant(value);
jet.grad[param_idx] = 1.0;
jet
}
}
impl<const N: usize, const H: usize> Jet2<N, H> {
pub fn constant(value: f64) -> Self {
Self {
value,
grad: [0.0; N],
hess: [0.0; H],
}
}
pub fn variable(value: f64, param_idx: usize) -> Self {
let mut jet = Self::constant(value);
jet.grad[param_idx] = 1.0;
jet
}
}
#[inline(always)]
pub fn tens_index(i: usize, j: usize, k: usize) -> Option<usize> {
let (mut a, mut b, mut c) = (i, j, k);
if a < b {
std::mem::swap(&mut a, &mut b);
}
if b < c {
std::mem::swap(&mut b, &mut c);
}
if a < b {
std::mem::swap(&mut a, &mut b);
}
Some(a * (a + 1) * (a + 2) / 6 + b * (b + 1) / 2 + c)
}
#[inline(always)]
pub fn tens_idx(i: usize, j: usize, k: usize) -> usize {
i * (i + 1) * (i + 2) / 6 + j * (j + 1) / 2 + k
}
#[inline(always)]
pub const fn tens_size(n: usize) -> usize {
n * (n + 1) * (n + 2) / 6
}
#[derive(Clone, Copy)]
pub struct Jet3<const N: usize, const H: usize, const T: usize> {
pub value: f64,
pub grad: [f64; N],
pub hess: [f64; H],
pub tens: [f64; T],
}
impl<const N: usize, const H: usize, const T: usize> Jet3<N, H, T> {
pub fn constant(value: f64) -> Self {
Self {
value,
grad: [0.0; N],
hess: [0.0; H],
tens: [0.0; T],
}
}
pub fn variable(value: f64, param_idx: usize) -> Self {
let mut jet = Self::constant(value);
jet.grad[param_idx] = 1.0;
jet
}
}
pub type Dual = Jet1<1>;
pub type HyperDual = Jet2<2, { hess_size(2) }>;
pub type HyperHyperDual = Jet3<2, { hess_size(2) }, { tens_size(2) }>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hess_index() {
assert_eq!(hess_index(0, 0).unwrap(), 0);
assert_eq!(hess_index(1, 0).unwrap(), 1);
assert_eq!(hess_index(1, 1).unwrap(), 2);
assert_eq!(hess_index(2, 0).unwrap(), 3);
assert_eq!(hess_index(2, 1).unwrap(), 4);
assert_eq!(hess_index(2, 2).unwrap(), 5);
for i in 0..5 {
for j in 0..5 {
assert_eq!(hess_index(i, j), hess_index(j, i))
}
}
}
#[test]
fn test_hess_size() {
assert_eq!(hess_size(1), 1);
assert_eq!(hess_size(2), 3);
assert_eq!(hess_size(3), 6);
assert_eq!(hess_size(4), 10);
}
#[test]
fn test_tens_index() {
assert_eq!(tens_index(0, 0, 0).unwrap(), 0);
assert_eq!(tens_index(1, 0, 0).unwrap(), 1);
assert_eq!(tens_index(1, 1, 0).unwrap(), 2);
assert_eq!(tens_index(1, 1, 1).unwrap(), 3);
assert_eq!(tens_index(2, 0, 0).unwrap(), 4);
assert_eq!(tens_index(2, 1, 0).unwrap(), 5);
assert_eq!(tens_index(2, 1, 1).unwrap(), 6);
assert_eq!(tens_index(2, 2, 0).unwrap(), 7);
assert_eq!(tens_index(2, 2, 1).unwrap(), 8);
assert_eq!(tens_index(2, 2, 2).unwrap(), 9);
for i in 0..4 {
for j in 0..4 {
for k in 0..4 {
assert_eq!(tens_index(i, j, k), tens_index(i, k, j));
assert_eq!(tens_index(i, j, k), tens_index(j, i, k));
assert_eq!(tens_index(i, j, k), tens_index(j, k, i));
assert_eq!(tens_index(i, j, k), tens_index(k, i, j));
assert_eq!(tens_index(i, j, k), tens_index(k, j, i));
}
}
}
}
#[test]
fn test_tens_size() {
assert_eq!(tens_size(1), 1);
assert_eq!(tens_size(2), 4);
assert_eq!(tens_size(3), 10);
assert_eq!(tens_size(6), 56);
assert_eq!(tens_size(9), 165);
}
use crate::traits::ThirdOrder;
const COMP_TOL: f64 = 1e-10;
fn approx(actual: f64, expected: f64, what: &str) {
let tol = COMP_TOL * expected.abs().max(1.0);
assert!(
(actual - expected).abs() <= tol,
"{}: expected {}, got {} (|diff| = {:e})",
what,
expected,
actual,
(actual - expected).abs()
);
}
#[test]
fn jet3_composition_sin_xy_plus_exp_z_y2() {
type J3 = Jet3<3, { hess_size(3) }, { tens_size(3) }>;
let a: f64 = 0.9;
let b: f64 = 1.3;
let d: f64 = 0.4;
let x = J3::variable(a, 0);
let y = J3::variable(b, 1);
let z = J3::variable(d, 2);
let f = (x * y).sin() + z.exp() * (y * y);
let s = (a * b).sin();
let c = (a * b).cos();
let v = d.exp();
approx(f.value, s + v * b * b, "value");
approx(f.grad[0], c * b, "∂/∂x");
approx(f.grad[1], c * a + 2.0 * v * b, "∂/∂y");
approx(f.grad[2], v * b * b, "∂/∂z");
approx(f.hess[0], -s * b * b, "∂²/∂x²");
approx(f.hess[1], c - s * a * b, "∂²/∂x∂y");
approx(f.hess[2], -s * a * a + 2.0 * v, "∂²/∂y²");
approx(f.hess[3], 0.0, "∂²/∂x∂z");
approx(f.hess[4], 2.0 * v * b, "∂²/∂y∂z");
approx(f.hess[5], v * b * b, "∂²/∂z²");
approx(f.tens[0], -c * b.powi(3), "∂³/∂x³");
approx(f.tens[1], -c * a * b * b - 2.0 * s * b, "∂³/∂y∂x²");
approx(f.tens[2], -2.0 * s * a - c * a * a * b, "∂³/∂y²∂x");
approx(f.tens[3], -c * a.powi(3), "∂³/∂y³");
approx(f.tens[4], 0.0, "∂³/∂z∂x²");
approx(f.tens[5], 0.0, "∂³/∂z∂y∂x");
approx(f.tens[6], 2.0 * v, "∂³/∂z∂y²");
approx(f.tens[7], 0.0, "∂³/∂z²∂x");
approx(f.tens[8], 2.0 * v * b, "∂³/∂z²∂y");
approx(f.tens[9], v * b * b, "∂³/∂z³");
}
#[test]
fn jet3_tens_accessor_is_fully_symmetric() {
type J3 = Jet3<3, { hess_size(3) }, { tens_size(3) }>;
let x = J3::variable(0.9, 0);
let y = J3::variable(1.3, 1);
let z = J3::variable(0.4, 2);
let f = (x * y).sin() + z.exp() * (y * y);
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
let v_ijk = f.tens(i, j, k);
assert!(
(v_ijk - f.tens(i, k, j)).abs() < 1e-14,
"tens({i},{j},{k}) != tens({i},{k},{j})"
);
assert!(
(v_ijk - f.tens(j, i, k)).abs() < 1e-14,
"tens({i},{j},{k}) != tens({j},{i},{k})"
);
assert!(
(v_ijk - f.tens(j, k, i)).abs() < 1e-14,
"tens({i},{j},{k}) != tens({j},{k},{i})"
);
assert!(
(v_ijk - f.tens(k, i, j)).abs() < 1e-14,
"tens({i},{j},{k}) != tens({k},{i},{j})"
);
assert!(
(v_ijk - f.tens(k, j, i)).abs() < 1e-14,
"tens({i},{j},{k}) != tens({k},{j},{i})"
);
}
}
}
}
}