#![cfg(feature = "testing")]
use diffable::{
complex::Complex,
coords::Coords,
epsilon_metric::R64,
traits::{
BothSided, Cat, Dual, DualSinistered, Dualized, Euclidean, Field, Form, Left, Normalize,
NormalizeWith, Right, Sinister, Sinistered, Tensor, Undecorated, Vector,
calculus::{
Contract, Here, JetVector, OnLeft, Reassociate, Swap, TensorProduct, ThroughSinister, d,
},
},
};
use num_traits::Zero;
#[test]
fn reassociation_is_selected_by_a_tree_path() {
type A = Coords<f64, 2>;
type B = Dual<Coords<f64, 3>>;
type C = Dual<Coords<f64, 5>>;
type LeftAssociated = TensorProduct<TensorProduct<A, B>, C>;
let tensor = LeftAssociated::from_fn(|i| i as f64);
let right = tensor.reassociate();
let left = right.reassociate();
assert!(left.iter().copied().eq((0..30).map(|i| i as f64)));
type Outer = TensorProduct<LeftAssociated, Dual<Coords<f64, 7>>>;
let tensor = Outer::from_fn(|i| i as f64);
let reassociated = tensor.reassociate::<OnLeft<Right>>();
assert!(reassociated.iter().copied().eq((0..210).map(|i| i as f64)));
}
#[test]
fn contraction_is_selected_by_the_same_tree_paths() {
type V = Coords<f64, 2>;
type W = Coords<f64, 3>;
type X = Coords<f64, 5>;
let endomorphism = TensorProduct::<V, Dual<V>>::from_iter([1.0, 2.0, 3.0, 4.0]);
let trace = endomorphism.contract();
assert_eq!(trace, 5.0);
type Tensor = TensorProduct<TensorProduct<W, Dual<W>>, Dual<X>>;
let tensor = Tensor::from_fn(|i| i as f64);
let contracted = tensor.contract();
assert!(
contracted
.iter()
.copied()
.eq([60.0, 63.0, 66.0, 69.0, 72.0])
);
type Opposite = TensorProduct<Dual<Sinister<V>>, Sinister<V>>;
let tensor = Opposite::from_iter([1.0, 2.0, 3.0, 4.0]);
let trace = tensor.contract();
assert_eq!(trace, 5.0);
type OnTheRight = TensorProduct<W, Sinister<TensorProduct<V, Dual<V>>>>;
let tensor = OnTheRight::from_fn(|i| i as f64);
let contracted = tensor.contract();
assert!(contracted.iter().copied().eq([3.0, 11.0, 19.0]));
type Deep = TensorProduct<TensorProduct<TensorProduct<V, Dual<V>>, Dual<W>>, Dual<X>>;
let tensor = Deep::from_fn(|i| i as f64);
let contracted = tensor.contract();
assert!(
contracted
.iter()
.copied()
.eq((0..W::N * X::N).map(|i| 45.0 + 2.0 * i as f64))
);
}
#[test]
fn matrix_multiplication_is_tensor_contraction() {
type F = f64;
type V = Coords<F, 2>;
type Endomorphism = TensorProduct<V, Dual<V>>;
type Product = TensorProduct<Endomorphism, Sinister<Endomorphism>>;
let a = Endomorphism::from_iter([1.0, 2.0, 3.0, 4.0]);
let b = Sinister(Endomorphism::from_iter([5.0, 6.0, 7.0, 8.0]));
let tensor = Product::pure(a, b);
assert!(tensor.iter().copied().eq([
5.0, 6.0, 7.0, 8.0, 10.0, 12.0, 14.0, 16.0, 15.0, 18.0, 21.0, 24.0, 20.0, 24.0, 28.0, 32.0,
]));
let product: Endomorphism = tensor
.reassociate::<Left>()
.reassociate::<OnLeft<Right>>()
.contract();
let expected = Endomorphism::from_iter([19.0, 22.0, 43.0, 50.0]);
assert!(product.iter().eq(expected.iter()));
}
#[test]
fn reassociation_exposes_the_other_contraction() {
type V = Coords<f64, 2>;
type Tensor = TensorProduct<TensorProduct<V, Dual<V>>, Sinister<V>>;
let tensor = Tensor::from_fn(|i| i as f64);
let sinister = tensor.swap::<OnLeft<Here>>().swap::<Here>();
let first_pair = tensor.contract();
let first_pair_sinister = sinister.contract();
assert_eq!(first_pair, first_pair_sinister);
let second_pair = tensor.reassociate().contract();
let second_pair_sinister = sinister.reassociate().contract();
assert_eq!(second_pair, second_pair_sinister);
assert!(first_pair.iter().copied().eq([6.0, 8.0]));
assert!(second_pair.iter().copied().eq([3.0, 11.0]));
}
#[test]
fn normalization_reduces_an_entire_tensor_tree() {
type V = Coords<f64, 2>;
type Raw = TensorProduct<Sinister<Sinister<V>>, Dual<Dual<Dual<V>>>>;
type Canonical = TensorProduct<V, Dual<V>>;
let raw = Raw::from_iter([1.0, 2.0, 3.0, 4.0]);
let canonical: Canonical = raw.normalize();
assert!(canonical.iter().copied().eq([1.0, 2.0, 3.0, 4.0]));
}
#[test]
fn differential_of_scalar_linear_function() {
fn double<V: Euclidean>(x: V) -> V {
x * V::F::from_nat(2)
}
fn double_v<V: Vector>(x: V) -> V {
x * V::F::from_nat(2)
}
let derivative = d(double).at(Coords::from(0.0));
let derivative_v_real = d(double_v).at(Coords::from(0.0));
let derivative_v = d(double_v).at(Sinister(Coords::from(Complex::zero()).flat()));
assert_eq!(derivative[0], 2.0);
assert_eq!(derivative_v_real[0], 2.0);
assert_eq!(derivative_v[0], Complex::from_fixed(2.0));
}
#[test]
fn differential_computes_a_multivariate_hessian() {
fn f<V: Euclidean>(v: V) -> V {
let [x, y] = [v[0], v[1]];
V::from_iter([x * x * y, x * y * y])
}
let hessian = d(d(f)).at(Coords([2.0, 3.0]));
assert!(hessian.iter().eq(&[6.0, 4.0, 4.0, 0.0, 0.0, 6.0, 6.0, 4.0]));
}
#[test]
fn differential_along_a_direction() {
fn f<V: Euclidean>(v: V) -> V {
V::from_iter([v[0] * v[0] + v[1], v[0] * v[1]])
}
let point = Coords([2.0, 3.0]);
let direction = Coords([5.0, 7.0]);
let derivative = d(f).along(direction).at(point);
assert!(derivative.iter().copied().eq([27.0, 29.0]));
}
#[test]
fn directional_derivative_composes_with_differentiation() {
fn cube<V: Euclidean>(x: V) -> V {
x.map(|x| x.powi(3))
}
let direction = Coords::from(4.0);
let derivative = d(d(d(cube).along(direction))).at(Coords::from(7.0));
assert_eq!(derivative[0], 24.0);
}
#[test]
fn direction_and_point_can_vary_together() {
fn cube<V: Euclidean>(x: V) -> V {
x.map(|x| x.powi(3))
}
let derivative = d(|v| d(cube).along(v).at(v)).at(Coords::from(7.0));
assert_eq!(derivative[0], 441.0);
}
#[test]
fn second_derivative_of_square() {
fn square<V: Vector>(x: V) -> V {
V::from_fn(|_| x[0] * x[0])
}
let second = d(d(square)).at(Coords::from(3.0));
assert_eq!(second[0], 2.0);
}
#[test]
fn third_derivative_of_cube() {
fn cube<V: Vector>(x: V) -> V {
x.map(|x| x.powi(3))
}
let second = d(d(d(cube))).at(Coords::from(-6.0));
assert_eq!(second[0], 6.0);
}
#[test]
fn differential_computes_a_jacobian() {
fn f<V: Euclidean>(v: V) -> V {
V::from_iter([v[0] * v[0] + v[1], v[0] * v[1]])
}
let jacobian = d(f).at(Coords([2.0, 3.0]));
assert_eq!(jacobian[0], 4.0);
assert_eq!(jacobian[1], 1.0);
assert_eq!(jacobian[2], 3.0);
assert_eq!(jacobian[3], 2.0);
}
#[test]
fn jacobian_uses_output_by_input_orientation() {
fn f<V: Euclidean>(v: V) -> V {
let to = |n| V::F::from_nat(n);
V::from_iter([v[0] * to(2) + v[1] * to(3), v[0] * to(5) + v[1] * to(7)])
}
let jacobian = d(f).at(Coords([11.0, 13.0]));
assert_eq!(jacobian[0], 2.0);
assert_eq!(jacobian[1], 3.0);
assert_eq!(jacobian[2], 5.0);
assert_eq!(jacobian[3], 7.0);
}
#[test]
fn every_tensor_is_normalizable_without_rehanding() {
fn check<T>(x: T)
where
T: Tensor + Clone,
{
let _ = <T as NormalizeWith<Undecorated>>::normalize_with(x.clone());
let _ = <T as NormalizeWith<Dualized>>::normalize_with(x);
}
check(Coords::<f64, 2, 0>([1.0, 2.0]));
}
#[test]
fn every_two_sided_tensor_is_normalizable_in_every_decoration() {
fn check<T>(x: T)
where
T: Tensor<Action = BothSided> + Clone,
{
let _ = <T as NormalizeWith<Undecorated>>::normalize_with(x.clone());
let _ = <T as NormalizeWith<Dualized>>::normalize_with(x.clone());
let _ = <T as NormalizeWith<Sinistered>>::normalize_with(x.clone());
let _ = <T as NormalizeWith<DualSinistered>>::normalize_with(x);
}
check(Coords::<f64, 2>([1.0, 2.0]));
}
fn arbitrary_tensor_product_can_swap<A, B>(x: TensorProduct<A, B>)
where
A: Tensor<Action = BothSided, Hand = Right>,
B: Tensor<F = A::F, Action = BothSided, Hand = Left>,
{
let _ = x.swap::<Here>();
}
#[test]
fn swap_accepts_arbitrary_tensor_leaves() {
type V = Coords<R64, 2>;
let a = V::from_iter([1.0, 2.0].map(R64));
let b = V::from_iter([3.0, 4.0].map(R64));
arbitrary_tensor_product_can_swap(TensorProduct::pure(a, Sinister(b)));
}
#[allow(unused)]
fn contraction_descends_through_sinister<V>(x: Sinister<TensorProduct<V, Dual<V>>>)
where
V: Tensor<Hand = Right, Action = BothSided>,
{
let _ = x.contract::<ThroughSinister<Here>>();
}
#[allow(unused)]
fn swap_descends_through_sinister<V>(x: Sinister<TensorProduct<V, Sinister<V>>>)
where
V: Tensor<Action = BothSided, Hand = Right>,
{
let _ = x.swap::<ThroughSinister<Here>>();
}
#[allow(unused)]
fn jetted_tensor_product_can_swap<C: Cat, V>()
where
V: Tensor<Action = BothSided>,
JetVector<C, V>: Tensor<Action = BothSided, Hand = Right>,
{
fn check<T>()
where
T: Tensor<Action = BothSided, Hand = Right>,
TensorProduct<T, Sinister<T>>: Tensor,
{
}
check::<JetVector<C, V>>();
}