#![cfg_attr(
all(doc, not(doctest)),
feature(external_doc),
doc(include = "../Readme.md")
)]
use core::marker::PhantomData;
use std::ops;
pub mod traits;
use traits::Scalar;
use traits::{ROAble, RWAble, ToOwning, ToView};
pub mod owning_markers;
pub use owning_markers::{CompatibleWith, OwningMode, RO, RW};
#[derive(PartialEq, Debug, Clone, Copy, Hash, Default)]
pub struct Dual<T, M, F>
where
M: OwningMode,
T: CompatibleWith<M, F>,
F: Scalar,
{
content: T,
om: M,
ph_f: PhantomData<F>,
}
impl<T, M, F> From<T> for Dual<T, M, F>
where
M: OwningMode,
T: CompatibleWith<M, F>,
F: Scalar,
{
fn from(x: T) -> Self {
Dual {
content: x,
om: M::default(),
ph_f: PhantomData,
}
}
}
impl<F> Dual<Vec<F>, RW, F>
where
F: Scalar,
{
pub fn constant(value: F, ndiffs: usize) -> Self {
let mut res = Dual::from(vec![F::zero(); ndiffs + 1]);
res.content[0] = value;
res
}
}
mod array_constant_impl {
use super::{Dual, Scalar, RW};
macro_rules! impl_array {
($n:literal) => {
#[doc(hidden)]
impl<F> Dual<[F; $n], RW, F>
where
F: Scalar,
{
pub fn constant(value: F, ndiffs: usize) -> Self {
assert_eq!(ndiffs + 1, $n);
let mut res = Dual::from([F::zero(); $n]);
res.content[0] = value;
res
}
}
};
}
impl_array!(1);
impl_array!(2);
impl_array!(3);
impl_array!(4);
impl_array!(5);
impl_array!(6);
impl_array!(7);
impl_array!(8);
impl_array!(9);
impl_array!(10);
impl_array!(11);
impl_array!(12);
impl_array!(13);
impl_array!(14);
impl_array!(15);
impl_array!(16);
impl_array!(17);
impl_array!(18);
impl_array!(19);
impl_array!(20);
impl_array!(21);
impl_array!(22);
impl_array!(23);
impl_array!(24);
impl_array!(25);
impl_array!(26);
impl_array!(27);
impl_array!(28);
impl_array!(29);
impl_array!(30);
impl_array!(31);
impl_array!(32);
}
impl<T, M, F> Dual<T, M, F>
where
M: OwningMode,
T: ROAble<F>,
T: CompatibleWith<M, F>,
F: Scalar,
{
pub fn to_owning(&self) -> Dual<T::Owning, RW, F>
where
T: ToOwning<F>,
{
Dual::from(self.content.to_owning())
}
pub fn as_slice(&self) -> &[F] {
self.content.ro()
}
pub fn val(&self) -> F {
self.as_slice()[0]
}
pub fn diffs(&self) -> &[F] {
&self.as_slice()[1..]
}
pub fn ndiffs(&self) -> usize {
self.as_slice().len() - 1
}
pub fn is_close<S, M2>(&self, b: &Dual<S, M2, F>, atol: F) -> bool
where
M2: OwningMode,
S: ROAble<F>,
S: CompatibleWith<M2, F>,
{
self.as_slice()
.iter()
.zip(b.as_slice())
.all(|(xs, xb)| (*xs - *xb).abs() <= atol)
}
pub fn view<'a>(&'a self) -> Dual<&'a T::ViewType, RO, F>
where
T: ToView<F>,
&'a T::ViewType: CompatibleWith<RO, F>,
{
Dual::from(self.content.view())
}
pub fn into_container(self) -> T {
self.content
}
}
impl<T, F> Dual<T, RW, F>
where
T: RWAble<F>,
F: Scalar,
{
pub fn as_slice_mut(&mut self) -> &mut [F] {
self.content.rw()
}
pub fn val_mut(&mut self) -> &mut F {
unsafe { self.as_slice_mut().get_unchecked_mut(0) }
}
pub fn diffs_mut(&mut self) -> &mut [F] {
&mut self.as_slice_mut()[1..]
}
pub fn exp(mut self) -> Self {
let expval = self.val().exp();
*self.val_mut() = expval;
for x in self.diffs_mut() {
*x *= expval;
}
self
}
pub fn exp2(mut self) -> Self {
let expval = self.val().exp2();
*self.val_mut() = expval;
for x in self.diffs_mut() {
*x *= F::LN_2() * expval;
}
self
}
pub fn exp_base(mut self, base: F) -> Self {
let expval = base.powf(self.val());
*self.val_mut() = expval;
for x in self.diffs_mut() {
*x *= base.ln() * expval;
}
self
}
pub fn ln(mut self) -> Self {
let val = self.val();
*self.val_mut() = val.ln();
for x in self.diffs_mut() {
*x /= val;
}
self
}
pub fn inv(mut self) -> Self {
let vs = self.val();
let svs = vs * vs;
*self.val_mut() = F::one() / vs;
self.diffs_mut()
.iter_mut()
.for_each(|ds| *ds *= -F::one() / svs);
self
}
pub fn powf(mut self, exp: F) -> Self {
let vs = self.val();
*self.val_mut() = vs.powf(exp);
self.diffs_mut()
.iter_mut()
.for_each(|ds| *ds *= exp * vs.powf(exp - F::one()));
self
}
pub fn powdual<S, M2>(mut self, exp: Dual<S, M2, F>) -> Self
where
M2: OwningMode,
S: ROAble<F>,
S: CompatibleWith<M2, F>,
{
let vs = self.val();
if vs == F::zero() {
for ds in self.diffs_mut() {
*ds = F::zero()
}
return self;
}
let ve = exp.val();
*self.val_mut() = vs.powf(ve);
self.diffs_mut()
.iter_mut()
.zip(exp.diffs())
.for_each(|(ds, de)| *ds = vs.powf(ve - F::one()) * (vs * de * vs.ln() + ve * *ds));
self
}
pub fn abs(self) -> Self {
let v = self.val();
if v < F::zero() {
-self
} else {
self
}
}
}
impl<T, F> ops::Neg for Dual<T, RW, F>
where
T: RWAble<F>,
F: Scalar,
{
type Output = Self;
fn neg(mut self) -> Self {
for x in self.as_slice_mut() {
*x = ops::Neg::neg(*x);
}
self
}
}
#[cfg(feature = "implicit-clone")]
mod implicit_clone {
use super::*;
macro_rules! clone_impl {
{$fname: ident($($param : ident : $ptype : ty),*)} => {
pub fn $fname(&self,$($param : $ptype),*) -> Dual<T::Owning, RW, F> {
let res = self.to_owning();
res.$fname($($param),*)
}
}
}
impl<T, F> Dual<T, RO, F>
where
T: ToOwning<F>,
F: Scalar,
{
clone_impl!(exp());
clone_impl!(exp2());
clone_impl!(exp_base(base: F));
clone_impl!(ln());
clone_impl!(inv());
clone_impl!(powf(exp: F));
clone_impl!(abs());
pub fn powdual<S, M2>(self, exp: Dual<S, M2, F>) -> Dual<T::Owning, RW, F>
where
M2: OwningMode,
S: ROAble<F>,
S: CompatibleWith<M2, F>,
{
let res = self.to_owning();
res.powdual(exp)
}
}
impl<T, F> ops::Neg for Dual<T, RO, F>
where
T: ToOwning<F>,
F: Scalar,
{
type Output = Dual<T::Owning, RW, F>;
fn neg(self) -> Self::Output {
let res = self.to_owning();
-res
}
}
}
mod generate_duals;
mod impl_ops_dual;
mod impl_ops_scalar_rhs;
pub mod instanciations;
#[cfg(test)]
mod tests {
use super::instanciations::vecf64::Owning;
use super::*;
fn generate() -> Dual<Vec<f64>, RW, f64> {
let mut x = Owning::constant(42., 3);
x.diffs_mut()[0] = 17.;
x.diffs_mut()[2] = -7.;
x
}
#[test]
fn test_constant() {
let x = Owning::constant(42., 2);
assert_eq!(x, Owning::from(vec![42., 0., 0.]));
}
#[test]
fn test_size() {
let x = Owning::constant(0., 42);
assert_eq!(x.ndiffs(), 42);
}
#[test]
fn test_neg() {
let x = generate();
assert_eq!(-(-x.clone()), x);
}
#[test]
fn test_ln_exp() {
let x = generate();
assert!(x.clone().is_close(&x.clone().exp().ln(), 1e-8));
assert!(x.clone().is_close(&x.clone().ln().exp(), 1e-8));
}
}