use crate::{variable::Variable, vertex::Arity};
use std::iter::{Product, Sum};
use std::ops::Neg;
use std::ops::{Add, AddAssign};
use std::ops::{Div, DivAssign};
use std::ops::{Mul, MulAssign};
use std::ops::{Sub, SubAssign};
impl<'v> AddAssign<Variable<'v>> for Variable<'v> {
#[inline]
fn add_assign(&mut self, other: Variable<'v>) {
assert!(std::ptr::eq(self.graph, other.graph));
*self = *self + other;
}
}
impl<'v> AddAssign<f64> for Variable<'v> {
#[inline]
fn add_assign(&mut self, other: f64) {
*self = *self + other;
}
}
impl<'v> AddAssign<Variable<'v>> for f64 {
#[inline]
fn add_assign(&mut self, other: Variable<'v>) {
*self = *self + other.value;
}
}
impl<'v> Add<Variable<'v>> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn add(self, other: Variable<'v>) -> Self::Output {
assert!(std::ptr::eq(self.graph, other.graph));
Variable {
graph: self.graph,
value: self.value + other.value,
index: self
.graph
.push(Arity::Binary, &[self.index, other.index], &[1.0, 1.0]),
}
}
}
impl<'v> Add<f64> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn add(self, other: f64) -> Self::Output {
Variable {
graph: self.graph,
value: self.value + other,
index: self
.graph
.push(Arity::Binary, &[self.index, self.index], &[1.0, 0.0]),
}
}
}
impl<'v> Add<Variable<'v>> for f64 {
type Output = Variable<'v>;
#[inline]
fn add(self, other: Variable<'v>) -> Self::Output {
other + self
}
}
impl<'v> DivAssign<Variable<'v>> for Variable<'v> {
#[inline]
fn div_assign(&mut self, other: Variable<'v>) {
assert!(std::ptr::eq(self.graph, other.graph));
*self = *self / other;
}
}
impl<'v> DivAssign<f64> for Variable<'v> {
#[inline]
fn div_assign(&mut self, other: f64) {
*self = *self / other;
}
}
impl<'v> DivAssign<Variable<'v>> for f64 {
#[inline]
fn div_assign(&mut self, other: Variable<'v>) {
*self = *self / other.value;
}
}
impl<'v> Div<Variable<'v>> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn div(self, other: Variable<'v>) -> Self::Output {
assert!(std::ptr::eq(self.graph, other.graph));
self * other.recip()
}
}
impl<'v> Div<f64> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
#[allow(clippy::suspicious_arithmetic_impl)]
fn div(self, other: f64) -> Self::Output {
self * other.recip()
}
}
impl<'v> Div<Variable<'v>> for f64 {
type Output = Variable<'v>;
#[inline]
fn div(self, other: Variable<'v>) -> Self::Output {
Variable {
graph: other.graph,
value: self / other.value,
index: other.graph.push(
Arity::Binary,
&[other.index, other.index],
&[0.0, -self / (other.value * other.value)],
),
}
}
}
impl<'v> std::ops::Neg for Variable<'v> {
type Output = Self;
#[inline]
fn neg(self) -> Self::Output {
self * -1.0
}
}
impl<'v> Variable<'v> {
#[must_use]
#[inline]
pub fn abs(self) -> Self {
Variable {
graph: self.graph,
value: self.value.abs(),
index: self
.graph
.push(Arity::Unary, &[self.index], &[self.value.signum()]),
}
}
#[must_use]
#[inline]
pub fn acos(self) -> Self {
Variable {
graph: self.graph,
value: self.value.acos(),
index: self.graph.push(
Arity::Unary,
&[self.index],
&[((1.0 - self.value.powi(2)).sqrt()).recip().neg()],
),
}
}
#[must_use]
#[inline]
pub fn acosh(self) -> Self {
Variable {
graph: self.graph,
value: self.value.acosh(),
index: self.graph.push(
Arity::Unary,
&[self.index],
&[((self.value - 1.0).sqrt() * (self.value + 1.0).sqrt()).recip()],
),
}
}
#[must_use]
#[inline]
pub fn asin(self) -> Self {
Variable {
graph: self.graph,
value: self.value.asin(),
index: self.graph.push(
Arity::Unary,
&[self.index],
&[if (self.value > -1.0) && (self.value < 1.0) {
((1.0 - self.value.powi(2)).sqrt()).recip()
} else {
f64::NAN
}],
),
}
}
#[must_use]
#[inline]
pub fn asinh(self) -> Self {
Variable {
graph: self.graph,
value: self.value.asinh(),
index: self.graph.push(
Arity::Unary,
&[self.index],
&[((1.0 + self.value.powi(2)).sqrt()).recip()],
),
}
}
#[must_use]
#[inline]
pub fn atan(self) -> Self {
Variable {
graph: self.graph,
value: self.value.atan(),
index: self.graph.push(
Arity::Unary,
&[self.index],
&[((1.0 + self.value.powi(2)).recip())],
),
}
}
#[must_use]
#[inline]
pub fn atanh(self) -> Self {
Variable {
graph: self.graph,
value: self.value.atanh(),
index: self.graph.push(
Arity::Unary,
&[self.index],
&[((1.0 - self.value.powi(2)).recip())],
),
}
}
#[must_use]
#[inline]
pub fn cbrt(self) -> Self {
Variable {
graph: self.graph,
value: self.value.cbrt(),
index: self.graph.push(
Arity::Unary,
&[self.index],
&[((3.0 * self.value.powf(2.0 / 3.0)).recip())],
),
}
}
#[must_use]
#[inline]
pub fn cos(self) -> Self {
Variable {
graph: self.graph,
value: self.value.cos(),
index: self
.graph
.push(Arity::Unary, &[self.index], &[self.value.sin().neg()]),
}
}
#[must_use]
#[inline]
pub fn cosh(self) -> Self {
Variable {
graph: self.graph,
value: self.value.cosh(),
index: self
.graph
.push(Arity::Unary, &[self.index], &[self.value.sinh()]),
}
}
#[must_use]
#[inline]
pub fn exp(self) -> Self {
Variable {
graph: self.graph,
value: self.value.exp(),
index: self
.graph
.push(Arity::Unary, &[self.index], &[self.value.exp()]),
}
}
#[must_use]
#[inline]
pub fn exp2(self) -> Self {
Variable {
graph: self.graph,
value: self.value.exp2(),
index: self.graph.push(
Arity::Unary,
&[self.index],
&[2_f64.powf(self.value) * 2_f64.ln()],
),
}
}
#[must_use]
#[inline]
pub fn exp_m1(self) -> Self {
Variable {
graph: self.graph,
value: self.value.exp_m1(),
index: self
.graph
.push(Arity::Unary, &[self.index], &[self.value.exp()]),
}
}
#[must_use]
#[inline]
pub fn ln(self) -> Self {
Variable {
graph: self.graph,
value: self.value.ln(),
index: self
.graph
.push(Arity::Unary, &[self.index], &[self.value.recip()]),
}
}
#[must_use]
#[inline]
pub fn ln_1p(self) -> Self {
Variable {
graph: self.graph,
value: self.value.ln_1p(),
index: self
.graph
.push(Arity::Unary, &[self.index], &[(1.0 + self.value).recip()]),
}
}
#[must_use]
#[inline]
pub fn log10(self) -> Self {
Variable {
graph: self.graph,
value: self.value.log10(),
index: self
.graph
.push(Arity::Unary, &[self.index], &[self.value.recip()]),
}
}
#[must_use]
#[inline]
pub fn log2(self) -> Self {
Variable {
graph: self.graph,
value: self.value.log2(),
index: self
.graph
.push(Arity::Unary, &[self.index], &[self.value.recip()]),
}
}
#[must_use]
#[inline]
pub fn recip(self) -> Self {
Variable {
graph: self.graph,
value: self.value.recip(),
index: self.graph.push(
Arity::Unary,
&[self.index],
&[self.value.powi(2).recip().neg()],
),
}
}
#[must_use]
#[inline]
pub fn sin(self) -> Self {
Variable {
graph: self.graph,
value: self.value.sin(),
index: self
.graph
.push(Arity::Unary, &[self.index], &[self.value.cos()]),
}
}
#[must_use]
#[inline]
pub fn sinh(self) -> Self {
Variable {
graph: self.graph,
value: self.value.sinh(),
index: self
.graph
.push(Arity::Unary, &[self.index], &[self.value.cosh()]),
}
}
#[must_use]
#[inline]
pub fn sqrt(self) -> Self {
Variable {
graph: self.graph,
value: self.value.sqrt(),
index: self.graph.push(
Arity::Unary,
&[self.index],
&[(2.0 * self.value.sqrt()).recip()],
),
}
}
#[must_use]
#[inline]
pub fn tan(self) -> Self {
Variable {
graph: self.graph,
value: self.value.tan(),
index: self.graph.push(
Arity::Unary,
&[self.index],
&[(self.value.cos().powi(2)).recip()],
),
}
}
#[must_use]
#[inline]
pub fn tanh(self) -> Self {
Variable {
graph: self.graph,
value: self.value.tanh(),
index: self.graph.push(
Arity::Unary,
&[self.index],
&[(self.value.cosh().powi(2)).recip()],
),
}
}
}
impl<'v> Sum<Variable<'v>> for Variable<'v> {
#[inline]
fn sum<I: Iterator<Item = Variable<'v>>>(iter: I) -> Self {
iter.reduce(|x, y| x + y)
.expect("Cannot call sum() since vector is empty. Exiting ...")
}
}
impl<'v> Product<Variable<'v>> for Variable<'v> {
#[inline]
fn product<I: Iterator<Item = Variable<'v>>>(iter: I) -> Self {
iter.reduce(|x, y| x * y)
.expect("Cannot call product() since vector is empty. Exiting ...")
}
}
pub trait Log<T> {
type Output;
fn log(&self, base: T) -> Self::Output;
}
impl<'v> Log<Variable<'v>> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn log(&self, base: Variable) -> Self::Output {
Self::Output {
graph: self.graph,
value: f64::log(self.value, base.value),
index: self.graph.push(
Arity::Binary,
&[self.index, base.index],
&[
-f64::ln(self.value) / (base.value * f64::ln(base.value).powi(2)),
1.0 / (self.value * f64::ln(base.value)),
],
),
}
}
}
impl<'v> Log<Variable<'v>> for f64 {
type Output = Variable<'v>;
#[inline]
fn log(&self, base: Variable<'v>) -> Self::Output {
Self::Output {
graph: base.graph,
value: f64::log(*self, base.value),
index: base.graph.push(
Arity::Binary,
&[base.index, base.index],
&[
-f64::ln(*self) / (base.value * f64::ln(base.value).powi(2)),
0.0,
],
),
}
}
}
impl<'v> Log<f64> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn log(&self, base: f64) -> Self::Output {
Self::Output {
graph: self.graph,
value: f64::log(self.value, base),
index: self.graph.push(
Arity::Binary,
&[self.index, self.index],
&[0.0, 1.0 / (f64::ln(base) * self.value)],
),
}
}
}
pub trait Min<T> {
type Output;
fn min(&self, other: T) -> Self::Output;
}
impl<'v> Min<Variable<'v>> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn min(&self, rhs: Variable<'v>) -> Self::Output {
assert!(std::ptr::eq(self.graph, rhs.graph));
Self::Output {
graph: self.graph,
value: self.value.min(rhs.value),
index: self.graph.push(
Arity::Binary,
&[self.index, rhs.index],
&[
if self.value < rhs.value { 1.0 } else { 0.0 },
if self.value > rhs.value { 1.0 } else { 0.0 },
],
),
}
}
}
impl<'v> Min<f64> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn min(&self, rhs: f64) -> Self::Output {
Self::Output {
graph: self.graph,
value: self.value.min(rhs),
index: self.graph.push(
Arity::Binary,
&[self.index, self.index],
&[if self.value < rhs { 1.0 } else { 0.0 }, 0.0],
),
}
}
}
impl<'v> Min<Variable<'v>> for f64 {
type Output = Variable<'v>;
#[inline]
fn min(&self, rhs: Variable<'v>) -> Self::Output {
Self::Output {
graph: rhs.graph,
value: f64::min(*self, rhs.value),
index: rhs.graph.push(
Arity::Binary,
&[rhs.index, rhs.index],
&[0.0, if self < &rhs.value { 1.0 } else { 0.0 }],
),
}
}
}
pub trait Max<T> {
type Output;
fn max(&self, other: T) -> Self::Output;
}
impl<'v> Max<Variable<'v>> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn max(&self, rhs: Variable<'v>) -> Self::Output {
assert!(std::ptr::eq(self.graph, rhs.graph));
Self::Output {
graph: self.graph,
value: self.value.max(rhs.value),
index: self.graph.push(
Arity::Binary,
&[self.index, rhs.index],
&[
if self.value > rhs.value { 1.0 } else { 0.0 },
if self.value < rhs.value { 1.0 } else { 0.0 },
],
),
}
}
}
impl<'v> Max<f64> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn max(&self, rhs: f64) -> Self::Output {
Self::Output {
graph: self.graph,
value: self.value.max(rhs),
index: self.graph.push(
Arity::Binary,
&[self.index, self.index],
&[if self.value > rhs { 1.0 } else { 0.0 }, 0.0],
),
}
}
}
impl<'v> Max<Variable<'v>> for f64 {
type Output = Variable<'v>;
#[inline]
fn max(&self, rhs: Variable<'v>) -> Self::Output {
Self::Output {
graph: rhs.graph,
value: f64::max(*self, rhs.value),
index: rhs.graph.push(
Arity::Binary,
&[rhs.index, rhs.index],
&[0.0, if self > &rhs.value { 1.0 } else { 0.0 }],
),
}
}
}
impl<'v> MulAssign<Variable<'v>> for Variable<'v> {
#[inline]
fn mul_assign(&mut self, other: Variable<'v>) {
assert!(std::ptr::eq(self.graph, other.graph));
*self = *self * other;
}
}
impl<'v> MulAssign<f64> for Variable<'v> {
#[inline]
fn mul_assign(&mut self, other: f64) {
*self = *self * other;
}
}
impl<'v> MulAssign<Variable<'v>> for f64 {
#[inline]
fn mul_assign(&mut self, other: Variable<'v>) {
*self = *self * other.value;
}
}
impl<'v> Mul<Variable<'v>> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn mul(self, other: Variable<'v>) -> Self::Output {
assert!(std::ptr::eq(self.graph, other.graph));
Variable {
graph: self.graph,
value: self.value * other.value,
index: self.graph.push(
Arity::Binary,
&[self.index, other.index],
&[other.value, self.value],
),
}
}
}
impl<'v> Mul<f64> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn mul(self, other: f64) -> Self::Output {
Variable {
graph: self.graph,
value: self.value * other,
index: self
.graph
.push(Arity::Binary, &[self.index, self.index], &[other, 0.0]),
}
}
}
impl<'v> Mul<Variable<'v>> for f64 {
type Output = Variable<'v>;
#[inline]
fn mul(self, other: Variable<'v>) -> Self::Output {
other * self
}
}
pub trait Powf<T> {
type Output;
fn powf(&self, other: T) -> Self::Output;
}
impl<'v> Powf<Variable<'v>> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn powf(&self, other: Variable<'v>) -> Self::Output {
assert!(std::ptr::eq(self.graph, other.graph));
Self::Output {
graph: self.graph,
value: self.value.powf(other.value),
index: self.graph.push(
Arity::Binary,
&[self.index, other.index],
&[
other.value * f64::powf(self.value, other.value - 1.),
f64::powf(self.value, other.value) * f64::ln(self.value),
],
),
}
}
}
impl<'v> Powf<f64> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn powf(&self, n: f64) -> Self::Output {
Self::Output {
graph: self.graph,
value: f64::powf(self.value, n),
index: self.graph.push(
Arity::Binary,
&[self.index, self.index],
&[n * f64::powf(self.value, n - 1.0), 0.0],
),
}
}
}
impl<'v> Powf<Variable<'v>> for f64 {
type Output = Variable<'v>;
#[inline]
fn powf(&self, other: Variable<'v>) -> Self::Output {
Self::Output {
graph: other.graph,
value: f64::powf(*self, other.value),
index: other.graph.push(
Arity::Binary,
&[other.index, other.index],
&[0.0, other.value * f64::powf(*self, other.value - 1.0)],
),
}
}
}
pub trait Powi<T> {
type Output;
fn powi(&self, other: T) -> Self::Output;
}
impl<'v> Powi<Variable<'v>> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn powi(&self, other: Variable<'v>) -> Self::Output {
assert!(std::ptr::eq(self.graph, other.graph));
Self::Output {
graph: self.graph,
value: self.value.powf(other.value),
index: self.graph.push(
Arity::Binary,
&[self.index, other.index],
&[
other.value * f64::powf(self.value, other.value - 1.),
f64::powf(self.value, other.value) * f64::ln(self.value),
],
),
}
}
}
impl<'v> Powi<i32> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn powi(&self, n: i32) -> Self::Output {
Self::Output {
graph: self.graph,
value: f64::powi(self.value, n),
index: self.graph.push(
Arity::Binary,
&[self.index, self.index],
&[f64::from(n) * f64::powi(self.value, n - 1), 0.0],
),
}
}
}
impl<'v> Powi<Variable<'v>> for f64 {
type Output = Variable<'v>;
#[inline]
fn powi(&self, other: Variable<'v>) -> Self::Output {
Self::Output {
graph: other.graph,
value: f64::powf(*self, other.value),
index: other.graph.push(
Arity::Binary,
&[other.index, other.index],
&[0.0, other.value * f64::powf(*self, other.value - 1.0)],
),
}
}
}
use std::f64::consts::PI;
impl<'v> Variable<'v> {
#[must_use]
#[inline]
pub fn erf(self) -> Self {
Variable {
graph: self.graph,
value: errorfunctions::RealErrorFunctions::erf(self.value),
index: self.graph.push(
Arity::Unary,
&[self.index],
&[2.0 * self.value.powi(2).neg().exp() / PI.sqrt()],
),
}
}
#[must_use]
#[inline]
pub fn erfc(self) -> Self {
Variable {
graph: self.graph,
value: errorfunctions::RealErrorFunctions::erfc(self.value),
index: self.graph.push(
Arity::Unary,
&[self.index],
&[((2.0 * self.value.powi(2).neg().exp()).neg() / PI.sqrt())],
),
}
}
}
impl<'v> SubAssign<Variable<'v>> for Variable<'v> {
#[inline]
fn sub_assign(&mut self, other: Variable<'v>) {
assert!(std::ptr::eq(self.graph, other.graph));
*self = *self - other;
}
}
impl<'v> SubAssign<f64> for Variable<'v> {
#[inline]
fn sub_assign(&mut self, other: f64) {
*self = *self - other;
}
}
impl<'v> SubAssign<Variable<'v>> for f64 {
#[inline]
fn sub_assign(&mut self, other: Variable<'v>) {
*self = *self - other.value;
}
}
impl<'v> Sub<Variable<'v>> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn sub(self, other: Variable<'v>) -> Self::Output {
assert!(std::ptr::eq(self.graph, other.graph));
self.add(other.neg())
}
}
impl<'v> Sub<f64> for Variable<'v> {
type Output = Variable<'v>;
#[inline]
fn sub(self, other: f64) -> Self::Output {
self.add(other.neg())
}
}
impl<'v> Sub<Variable<'v>> for f64 {
type Output = Variable<'v>;
#[inline]
fn sub(self, other: Variable<'v>) -> Self::Output {
Variable {
graph: other.graph,
value: self - other.value,
index: other
.graph
.push(Arity::Binary, &[other.index, other.index], &[0.0, -1.0]),
}
}
}
#[cfg(test)]
mod test_overloading {
use super::*;
use crate::*;
use RustQuant_utils::{assert_approx_equal, RUSTQUANT_EPSILON as EPS};
#[test]
fn test_div() {
let g = Graph::new();
let x = g.var(1.0);
let y = g.var(2.0);
let z = x / y;
let grad = z.accumulate();
assert_approx_equal!(z.value, 0.5, EPS);
assert_approx_equal!(grad.wrt(&x), 0.5, EPS);
assert_approx_equal!(grad.wrt(&y), -0.25, EPS);
let g = Graph::new();
let x = g.var(1.0);
let y = 2.0;
let z = x / y;
let grad = z.accumulate();
assert_approx_equal!(z.value, 0.5, EPS);
assert_approx_equal!(grad.wrt(&x), 0.5, EPS);
let g = Graph::new();
let x = 1.0;
let y = g.var(2.0);
let z = x / y;
let grad = z.accumulate();
assert_approx_equal!(z.value, 0.5, EPS);
assert_approx_equal!(grad.wrt(&y), -0.25, EPS);
}
#[test]
fn test_add() {
let g = Graph::new();
let x = g.var(1.0);
let y = g.var(2.0);
let z = x + y;
let grad = z.accumulate();
assert_approx_equal!(z.value, 3.0, EPS);
assert_approx_equal!(grad.wrt(&x), 1.0, EPS);
assert_approx_equal!(grad.wrt(&y), 1.0, EPS);
let g = Graph::new();
let x = g.var(1.0);
let y = 2.0;
let z = x + y;
let grad = z.accumulate();
assert_approx_equal!(z.value, 3.0, EPS);
assert_approx_equal!(grad.wrt(&x), 1.0, EPS);
let g = Graph::new();
let x = 1.0;
let y = g.var(2.0);
let z = x + y;
let grad = z.accumulate();
assert_approx_equal!(z.value, 3.0, EPS);
assert_approx_equal!(grad.wrt(&y), 1.0, EPS);
}
#[test]
fn test_values() {
let g = Graph::new();
let x = g.var(1.0);
assert_approx_equal!((-x).value, -1.0, EPS);
assert_approx_equal!(x.log2().value, 0.0, EPS);
assert_approx_equal!(x.exp2().value, 2.0, EPS);
assert_approx_equal!(x.exp_m1().value, 1.718_281_828_459_045, EPS);
assert_approx_equal!(x.ln().value, 0.0, EPS);
assert_approx_equal!(x.ln_1p().value, std::f64::consts::LN_2, EPS);
assert_approx_equal!(x.log10().value, 0.0, EPS);
assert_approx_equal!(x.log2().value, 0.0, EPS);
assert_approx_equal!(x.recip().value, 1.0, EPS);
assert_approx_equal!(x.sqrt().value, 1.0, EPS);
assert_approx_equal!(x.cbrt().value, 1.0, EPS);
assert_approx_equal!(x.sin().value, 0.841_470_984_807_896_5, EPS);
assert_approx_equal!(x.cos().value, 0.540_302_305_868_139_8, EPS);
assert_approx_equal!(x.tan().value, 1.557_407_724_654_902_3, EPS);
assert_approx_equal!(x.asin().value, std::f64::consts::FRAC_PI_2, EPS);
assert_approx_equal!(x.acos().value, 0.0, EPS);
assert_approx_equal!(x.atan().value, std::f64::consts::FRAC_PI_4, EPS);
assert_approx_equal!(x.sinh().value, 1.175_201_193_643_801_4, EPS);
assert_approx_equal!(x.cosh().value, 1.543_080_634_815_243_7, EPS);
assert_approx_equal!(x.tanh().value, 0.761_594_155_955_764_9, EPS);
assert_approx_equal!(x.asinh().value, 0.881_373_587_019_543, EPS);
assert_approx_equal!(x.acosh().value, 0.0, EPS);
assert!(x.atanh().is_infinite() && x.atanh().is_positive());
assert_approx_equal!(x.abs().value, 1.0, EPS);
}
#[test]
fn test_gradients() {
let g = Graph::new();
let x = g.var(1.0);
assert_approx_equal!((-x).accumulate().wrt(&x), -1.0, EPS);
assert_approx_equal!(x.log2().accumulate().wrt(&x), 1.0, EPS);
assert_approx_equal!(x.exp2().accumulate().wrt(&x), 1.386_294_361_119_890_6, EPS);
assert_approx_equal!(x.exp_m1().accumulate().wrt(&x), std::f64::consts::E, EPS);
assert_approx_equal!(x.ln().accumulate().wrt(&x), 1.0, EPS);
assert_approx_equal!(x.ln().accumulate().wrt(&x), 1.0, EPS);
assert_approx_equal!(x.ln_1p().accumulate().wrt(&x), 0.5, EPS);
assert_approx_equal!(x.log10().accumulate().wrt(&x), 1.0, EPS);
assert_approx_equal!(x.log2().accumulate().wrt(&x), 1.0, EPS);
assert_approx_equal!(x.recip().accumulate().wrt(&x), -1.0, EPS);
assert_approx_equal!(x.sqrt().accumulate().wrt(&x), 0.5, EPS);
assert_approx_equal!(x.cbrt().accumulate().wrt(&x), 0.333_333_333_333_333_3, EPS);
assert_approx_equal!(x.sin().accumulate().wrt(&x), 0.540_302_305_868_139_8, EPS);
assert_approx_equal!(x.cos().accumulate().wrt(&x), -0.841_470_984_807_896_5, EPS);
assert_approx_equal!(x.tan().accumulate().wrt(&x), 3.425_518_820_814_759, EPS);
assert_approx_equal!(x.sinh().accumulate().wrt(&x), 1.543_080_634_815_243_7, EPS);
assert_approx_equal!(x.atan().accumulate().wrt(&x), 0.5, EPS);
assert_approx_equal!(x.cosh().accumulate().wrt(&x), 1.175_201_193_643_801_4, EPS);
assert_approx_equal!(x.tanh().accumulate().wrt(&x), 0.419_974_341_614_026_14, EPS);
assert_approx_equal!(x.asinh().accumulate().wrt(&x), 1.0 / 2_f64.sqrt(), EPS);
assert_approx_equal!(x.abs().accumulate().wrt(&x), 1.0, EPS);
assert!(x.atanh().accumulate().wrt(&x).is_nan());
assert!(x.acosh().accumulate().wrt(&x).is_nan());
assert!(x.asin().accumulate().wrt(&x).is_nan());
assert!(x.acos().accumulate().wrt(&x).is_nan());
}
#[test]
fn test_sum() {
let g = Graph::new();
let params = (0..100).map(|x| g.var(f64::from(x))).collect::<Vec<_>>();
let sum = params.iter().copied().sum::<Variable>();
let derivs = sum.accumulate();
for i in derivs.wrt(¶ms) {
assert_approx_equal!(i, 1.0, EPS);
}
}
#[test]
fn test_product() {
let g = Graph::new();
let params = (1..=5).map(|x| g.var(f64::from(x))).collect::<Vec<_>>();
let prod = params.iter().copied().product::<Variable>();
let derivs = prod.accumulate();
let true_gradient = [120.0, 60.0, 40.0, 30.0, 24.0];
let expects = derivs.wrt(¶ms);
let n = expects.len();
let m = true_gradient.len();
assert_eq!(n, m);
for (&expect, &gradient) in expects.iter().zip(true_gradient.iter()) {
assert_approx_equal!(expect, gradient, EPS);
}
}
#[test]
fn test_values_ad() {
let g = Graph::new();
let x = g.var(1.0);
let y = g.var(2.0);
assert!(Min::min(&x, y) == 1.0);
assert!(Max::max(&x, y) == 2.0);
assert!(Min::min(&x, 2_f64) == 1.0);
assert!(Max::max(&x, 2_f64) == 2.0);
assert!(Max::max(&x, 2_f64) == 2.0);
assert!(Min::min(&2_f64, x) == 1.0);
assert!(Max::max(&2_f64, x) == 2.0);
}
#[test]
fn test_gradients_ad() {
let g = Graph::new();
let x = g.var(1.0);
let y = g.var(2.0);
assert_approx_equal!(Min::min(&x, y).accumulate().wrt(&x), 1.0, EPS);
assert_approx_equal!(Min::min(&x, y).accumulate().wrt(&y), 0.0, EPS);
assert_approx_equal!(Max::max(&x, y).accumulate().wrt(&x), 0.0, EPS);
assert_approx_equal!(Max::max(&x, y).accumulate().wrt(&y), 1.0, EPS);
assert_approx_equal!(Min::min(&x, 2_f64).accumulate().wrt(&x), 1.0, EPS);
assert_approx_equal!(Max::max(&x, 2_f64).accumulate().wrt(&x), 0.0, EPS);
assert_approx_equal!(Min::min(&2_f64, x).accumulate().wrt(&x), 0.0, EPS);
assert_approx_equal!(Max::max(&2_f64, x).accumulate().wrt(&x), 1.0, EPS);
}
#[test]
fn test_mul() {
let g = Graph::new();
let x = g.var(1.0);
let y = g.var(2.0);
let z = x * y;
let grad = z.accumulate();
assert_approx_equal!(z.value, 2.0, EPS);
assert_approx_equal!(grad.wrt(&x), 2.0, EPS);
assert_approx_equal!(grad.wrt(&y), 1.0, EPS);
let g = Graph::new();
let x = g.var(1.0);
let y = 2.0;
let z = x * y;
let grad = z.accumulate();
assert_approx_equal!(z.value, 2.0, EPS);
assert_approx_equal!(grad.wrt(&x), 2.0, EPS);
let g = Graph::new();
let x = 1.0;
let y = g.var(2.0);
let z = x * y;
let grad = z.accumulate();
assert_approx_equal!(z.value, 2.0, EPS);
assert_approx_equal!(grad.wrt(&y), 1.0, EPS);
}
#[test]
fn test_sub() {
let g = Graph::new();
let x = g.var(1.0);
let y = g.var(2.0);
let z = x - y;
let grad = z.accumulate();
assert!((z.value - -1.0).abs() < f64::EPSILON);
assert!((grad.wrt(&x) - 1.0).abs() < f64::EPSILON);
assert!((grad.wrt(&y) - -1.0).abs() < f64::EPSILON);
let g = Graph::new();
let x = g.var(1.0);
let y = 2.0;
let z = x - y;
let grad = z.accumulate();
assert!((z.value - -1.0).abs() < f64::EPSILON);
assert!((grad.wrt(&x) - 1.0).abs() < f64::EPSILON);
let g = Graph::new();
let x = 1.0;
let y = g.var(2.0);
let z = x - y;
let grad = z.accumulate();
assert!((z.value - -1.0).abs() < f64::EPSILON);
assert!((grad.wrt(&y) - -1.0).abs() < f64::EPSILON);
}
}