#![doc = include_str!("../README.md")]
#![no_std]
#![cfg_attr(docsrs, feature(doc_cfg))]
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign};
use thermite::vector::ops::{MulAddAssignExt, MulAddExt, Square};
pub mod ad;
pub mod math;
pub mod vector;
#[cfg(feature = "special")]
pub mod special;
pub use ad::AutoDiff;
pub use vector::DualFloatVector;
pub trait DualValue:
Copy
+ Add<Output = Self>
+ Sub<Output = Self>
+ Mul<Output = Self>
+ Div<Output = Self>
+ Neg<Output = Self>
+ MulAddExt<Self, Self, Output = Self>
{
const VAL_ZERO: Self;
const VAL_ONE: Self;
fn val_trunc(self) -> Self;
}
impl DualValue for f32 {
const VAL_ZERO: Self = 0.0;
const VAL_ONE: Self = 1.0;
#[inline(always)]
fn val_trunc(self) -> Self {
thermite::register::FloatElement::trunc(self)
}
}
impl DualValue for f64 {
const VAL_ZERO: Self = 0.0;
const VAL_ONE: Self = 1.0;
#[inline(always)]
fn val_trunc(self) -> Self {
thermite::register::FloatElement::trunc(self)
}
}
impl<R: thermite::register::FloatRegister> DualValue for thermite::prelude::Vector<R> {
const VAL_ZERO: Self = <Self as thermite::prelude::NumericVector>::ZERO;
const VAL_ONE: Self = <Self as thermite::prelude::NumericVector>::ONE;
#[inline(always)]
fn val_trunc(self) -> Self {
thermite::prelude::FloatVector::trunc(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[repr(C)]
pub struct Dual<V, const N: usize> {
pub re: V,
pub dual: [V; N],
}
impl<V: DualValue, const N: usize> Default for Dual<V, N> {
#[inline(always)]
fn default() -> Self {
Self::ZERO
}
}
impl<V: DualValue, const N: usize> Dual<V, N> {
pub const ZERO: Self = Self {
re: V::VAL_ZERO,
dual: [V::VAL_ZERO; N],
};
pub const ONE: Self = Self {
re: V::VAL_ONE,
dual: [V::VAL_ZERO; N],
};
#[inline(always)]
pub const fn constant(re: V) -> Self {
Self {
re,
dual: [V::VAL_ZERO; N],
}
}
#[inline(always)]
pub const fn new(re: V, dual: [V; N]) -> Self {
Self { re, dual }
}
#[inline(always)]
pub fn variable(re: V, i: usize) -> Self {
assert!(i < N, "dual variable index {i} out of range for N = {N}");
let mut dual = [V::VAL_ZERO; N];
dual[i] = V::VAL_ONE;
Self { re, dual }
}
#[inline(always)]
pub const fn value(self) -> V {
self.re
}
#[inline(always)]
pub const fn gradient(self) -> [V; N] {
self.dual
}
#[inline(always)]
pub fn chain(self, new_re: V, factor: V) -> Self {
let mut dual = self.dual;
let mut i = 0;
while i < N {
dual[i] = factor * dual[i];
i += 1;
}
Self { re: new_re, dual }
}
}
impl<V: DualValue, const N: usize> Neg for Dual<V, N> {
type Output = Self;
#[inline(always)]
fn neg(self) -> Self {
let mut dual = self.dual;
let mut i = 0;
while i < N {
dual[i] = -dual[i];
i += 1;
}
Self { re: -self.re, dual }
}
}
impl<V: DualValue, const N: usize> Add for Dual<V, N> {
type Output = Self;
#[inline(always)]
fn add(self, rhs: Self) -> Self {
let mut dual = self.dual;
let mut i = 0;
while i < N {
dual[i] = dual[i] + rhs.dual[i];
i += 1;
}
Self {
re: self.re + rhs.re,
dual,
}
}
}
impl<V: DualValue, const N: usize> Sub for Dual<V, N> {
type Output = Self;
#[inline(always)]
fn sub(self, rhs: Self) -> Self {
let mut dual = self.dual;
let mut i = 0;
while i < N {
dual[i] = dual[i] - rhs.dual[i];
i += 1;
}
Self {
re: self.re - rhs.re,
dual,
}
}
}
impl<V: DualValue, const N: usize> Mul for Dual<V, N> {
type Output = Self;
#[allow(clippy::suspicious_arithmetic_impl)]
#[inline(always)]
fn mul(self, rhs: Self) -> Self {
let mut dual = self.dual;
let mut i = 0;
while i < N {
dual[i] = self.re.mul_adde(rhs.dual[i], self.dual[i] * rhs.re);
i += 1;
}
Self {
re: self.re * rhs.re,
dual,
}
}
}
impl<V: DualValue, const N: usize> Div for Dual<V, N> {
type Output = Self;
#[allow(clippy::suspicious_arithmetic_impl)]
#[inline(always)]
fn div(self, rhs: Self) -> Self {
let q = self.re / rhs.re;
let inv = V::VAL_ONE / rhs.re;
let mut dual = self.dual;
let mut i = 0;
while i < N {
dual[i] = q.nmul_adde(rhs.dual[i], self.dual[i]) * inv;
i += 1;
}
Self { re: q, dual }
}
}
impl<V: DualValue, const N: usize> Add<V> for Dual<V, N> {
type Output = Self;
#[inline(always)]
fn add(self, rhs: V) -> Self {
Self {
re: self.re + rhs,
dual: self.dual,
}
}
}
impl<V: DualValue, const N: usize> Sub<V> for Dual<V, N> {
type Output = Self;
#[inline(always)]
fn sub(self, rhs: V) -> Self {
Self {
re: self.re - rhs,
dual: self.dual,
}
}
}
impl<V: DualValue, const N: usize> Mul<V> for Dual<V, N> {
type Output = Self;
#[inline(always)]
fn mul(self, rhs: V) -> Self {
let mut dual = self.dual;
let mut i = 0;
while i < N {
dual[i] = dual[i] * rhs;
i += 1;
}
Self {
re: self.re * rhs,
dual,
}
}
}
impl<V: DualValue, const N: usize> Div<V> for Dual<V, N> {
type Output = Self;
#[inline(always)]
fn div(self, rhs: V) -> Self {
let inv = V::VAL_ONE / rhs;
let mut dual = self.dual;
let mut i = 0;
while i < N {
dual[i] = dual[i] * inv;
i += 1;
}
Self {
re: self.re / rhs,
dual,
}
}
}
impl<V: DualValue, const N: usize, T> AddAssign<T> for Dual<V, N>
where
Self: Add<T, Output = Self>,
{
#[inline(always)]
fn add_assign(&mut self, rhs: T) {
*self = *self + rhs;
}
}
impl<V: DualValue, const N: usize, T> SubAssign<T> for Dual<V, N>
where
Self: Sub<T, Output = Self>,
{
#[inline(always)]
fn sub_assign(&mut self, rhs: T) {
*self = *self - rhs;
}
}
impl<V: DualValue, const N: usize, T> MulAssign<T> for Dual<V, N>
where
Self: Mul<T, Output = Self>,
{
#[inline(always)]
fn mul_assign(&mut self, rhs: T) {
*self = *self * rhs;
}
}
impl<V: DualValue, const N: usize, T> DivAssign<T> for Dual<V, N>
where
Self: Div<T, Output = Self>,
{
#[inline(always)]
fn div_assign(&mut self, rhs: T) {
*self = *self / rhs;
}
}
impl<V: DualValue, const N: usize> Rem for Dual<V, N> {
type Output = Self;
#[inline(always)]
fn rem(self, rhs: Self) -> Self {
let k = (self.re / rhs.re).val_trunc();
let mut dual = self.dual;
let mut i = 0;
while i < N {
dual[i] = k.nmul_adde(rhs.dual[i], self.dual[i]); i += 1;
}
Self {
re: k.nmul_adde(rhs.re, self.re), dual,
}
}
}
#[allow(clippy::suspicious_arithmetic_impl)]
impl<V: DualValue, const N: usize> Rem<V> for Dual<V, N> {
type Output = Self;
#[inline(always)]
fn rem(self, rhs: V) -> Self {
let k = (self.re / rhs).val_trunc();
Self {
re: k.nmul_adde(rhs, self.re), dual: self.dual,
}
}
}
impl<V: DualValue, const N: usize, T> RemAssign<T> for Dual<V, N>
where
Self: Rem<T, Output = Self>,
{
#[inline(always)]
fn rem_assign(&mut self, rhs: T) {
*self = *self % rhs;
}
}
macro_rules! dual_fma {
($($name:ident => $re_op:ident, $outer:ident, $inner:ident);* $(;)?) => {
$(
#[inline(always)]
fn $name(self, a: Self, b: Self) -> Self {
let re = self.re.$re_op(a.re, b.re);
let mut dual = self.dual;
let mut i = 0;
while i < N {
dual[i] = self.re.$outer(a.dual[i], self.dual[i].$inner(a.re, b.dual[i]));
i += 1;
}
Self { re, dual }
}
)*
};
}
#[rustfmt::skip]
impl<V: DualValue, const N: usize> MulAddExt<Self, Self> for Dual<V, N> {
type Output = Self;
const HAS_TRUE_FMA: bool = false;
dual_fma! {
mul_add => mul_add, mul_add, mul_add;
mul_sub => mul_sub, mul_add, mul_sub;
nmul_add => nmul_add, nmul_add, nmul_add;
nmul_sub => nmul_sub, nmul_sub, mul_add;
mul_adde => mul_adde, mul_adde, mul_adde;
mul_sube => mul_sube, mul_adde, mul_sube;
nmul_adde => nmul_adde, nmul_adde, nmul_adde;
nmul_sube => nmul_sube, nmul_sube, mul_adde;
}
}
#[rustfmt::skip]
impl<V: DualValue, const N: usize, A, B> MulAddAssignExt<A, B> for Dual<V, N>
where
Self: MulAddExt<A, B, Output = Self>,
{
#[inline(always)] fn mul_add_assign(&mut self, a: A, b: B) { *self = self.mul_add(a, b); }
#[inline(always)] fn mul_sub_assign(&mut self, a: A, b: B) { *self = self.mul_sub(a, b); }
#[inline(always)] fn nmul_add_assign(&mut self, a: A, b: B) { *self = self.nmul_add(a, b); }
#[inline(always)] fn nmul_sub_assign(&mut self, a: A, b: B) { *self = self.nmul_sub(a, b); }
#[inline(always)] fn mul_adde_assign(&mut self, a: A, b: B) { *self = self.mul_adde(a, b); }
#[inline(always)] fn mul_sube_assign(&mut self, a: A, b: B) { *self = self.mul_sube(a, b); }
#[inline(always)] fn nmul_adde_assign(&mut self, a: A, b: B) { *self = self.nmul_adde(a, b); }
#[inline(always)] fn nmul_sube_assign(&mut self, a: A, b: B) { *self = self.nmul_sube(a, b); }
}
impl<V: DualValue, const N: usize> Square for Dual<V, N> {
type Output = Self;
#[inline(always)]
fn square(self) -> Self {
let two_re = self.re + self.re;
let mut dual = self.dual;
let mut i = 0;
while i < N {
dual[i] = two_re * dual[i];
i += 1;
}
Self {
re: self.re * self.re,
dual,
}
}
}
impl<V: DualValue, const N: usize> DualValue for Dual<V, N> {
const VAL_ZERO: Self = Self::ZERO;
const VAL_ONE: Self = Self::ONE;
#[inline(always)]
fn val_trunc(self) -> Self {
Self {
re: self.re.val_trunc(),
dual: self.dual,
}
}
}