use numr::autograd::DualTensor;
use numr::dtype::DType;
use numr::error::Result;
use numr::ops::{ScalarOps, TensorOps, UtilityOps};
use numr::runtime::{Runtime, RuntimeClient};
use numr::tensor::Tensor;
use crate::common::jacobian::jacobian_autograd;
pub fn compute_jacobian_autograd<R, C, F>(
client: &C,
f: &F,
t: &Tensor<R>,
y: &Tensor<R>,
) -> Result<Tensor<R>>
where
R: Runtime<DType = DType>,
C: TensorOps<R> + RuntimeClient<R>,
F: Fn(&DualTensor<R>, &DualTensor<R>, &C) -> Result<DualTensor<R>>,
{
let t_dual = DualTensor::new(t.clone(), None);
jacobian_autograd(client, |y_dual, c| f(&t_dual, y_dual, c), y)
}
pub fn compute_norm<R, C>(client: &C, x: &Tensor<R>, p: f64) -> Result<Tensor<R>>
where
R: Runtime<DType = DType>,
C: TensorOps<R> + ScalarOps<R> + RuntimeClient<R>,
{
let x_abs = client.abs(x)?;
let x_pow = client.pow_scalar(&x_abs, p)?;
let sum = client.sum(&x_pow, &[0], false)?;
client.pow_scalar(&sum, 1.0 / p)
}
pub fn compute_norm_scalar<R, C>(client: &C, x: &Tensor<R>, p: f64) -> Result<f64>
where
R: Runtime<DType = DType>,
C: TensorOps<R> + ScalarOps<R> + RuntimeClient<R>,
{
let norm_tensor = compute_norm(client, x, p)?;
norm_tensor.item()
}
pub fn eval_primal<R, C, F>(client: &C, f: &F, t: &Tensor<R>, y: &Tensor<R>) -> Result<Tensor<R>>
where
R: Runtime<DType = DType>,
C: TensorOps<R> + RuntimeClient<R>,
F: Fn(&DualTensor<R>, &DualTensor<R>, &C) -> Result<DualTensor<R>>,
{
let t_dual = DualTensor::new(t.clone(), None);
let y_dual = DualTensor::new(y.clone(), None);
let result = f(&t_dual, &y_dual, client)?;
Ok(result.primal().clone())
}
pub fn compute_iteration_matrix<R, C>(
client: &C,
jacobian: &Tensor<R>,
h: f64,
beta: f64,
) -> Result<Tensor<R>>
where
R: Runtime<DType = DType>,
C: TensorOps<R> + ScalarOps<R> + UtilityOps<R> + RuntimeClient<R>,
{
let n = jacobian.shape()[0];
let identity = client.eye(n, None, DType::F64)?;
let h_beta = h * beta;
let scaled_j = client.mul_scalar(jacobian, h_beta)?;
client.sub(&identity, &scaled_j)
}
#[cfg(test)]
mod tests {
use super::*;
use numr::autograd::dual_ops::{dual_mul, dual_mul_scalar};
use numr::runtime::cpu::{CpuClient, CpuDevice, CpuRuntime};
fn setup() -> (CpuDevice, CpuClient) {
let device = CpuDevice::new();
let client = CpuClient::new(device.clone());
(device, client)
}
#[test]
fn test_jacobian_autograd_linear() {
let (device, client) = setup();
let t = Tensor::<CpuRuntime>::from_slice(&[0.0], &[1], &device);
let y = Tensor::<CpuRuntime>::from_slice(&[1.0, 2.0, 3.0], &[3], &device);
let f = |_t: &DualTensor<CpuRuntime>,
y: &DualTensor<CpuRuntime>,
c: &CpuClient|
-> Result<DualTensor<CpuRuntime>> { dual_mul_scalar(y, 2.0, c) };
let jac = compute_jacobian_autograd(&client, &f, &t, &y).unwrap();
let jac_data: Vec<f64> = jac.to_vec();
assert!((jac_data[0] - 2.0).abs() < 1e-10);
assert!((jac_data[4] - 2.0).abs() < 1e-10);
assert!((jac_data[8] - 2.0).abs() < 1e-10);
assert!(jac_data[1].abs() < 1e-10);
assert!(jac_data[2].abs() < 1e-10);
}
#[test]
fn test_jacobian_autograd_nonlinear() {
let (device, client) = setup();
let t = Tensor::<CpuRuntime>::from_slice(&[0.0], &[1], &device);
let y = Tensor::<CpuRuntime>::from_slice(&[1.0, 2.0, 3.0], &[3], &device);
let f = |_t: &DualTensor<CpuRuntime>,
y: &DualTensor<CpuRuntime>,
c: &CpuClient|
-> Result<DualTensor<CpuRuntime>> { dual_mul(y, y, c) };
let jac = compute_jacobian_autograd(&client, &f, &t, &y).unwrap();
let jac_data: Vec<f64> = jac.to_vec();
assert!((jac_data[0] - 2.0).abs() < 1e-10);
assert!((jac_data[4] - 4.0).abs() < 1e-10);
assert!((jac_data[8] - 6.0).abs() < 1e-10);
}
#[test]
fn test_jacobian_autograd_coupled() {
let (device, client) = setup();
let t = Tensor::<CpuRuntime>::from_slice(&[0.0], &[1], &device);
let y = Tensor::<CpuRuntime>::from_slice(&[1.0, 2.0], &[2], &device);
let f = |_t: &DualTensor<CpuRuntime>,
y: &DualTensor<CpuRuntime>,
c: &CpuClient|
-> Result<DualTensor<CpuRuntime>> {
let neg_y = dual_mul_scalar(y, -1.0, c)?;
Ok(neg_y)
};
let jac = compute_jacobian_autograd(&client, &f, &t, &y).unwrap();
let jac_data: Vec<f64> = jac.to_vec();
assert!((jac_data[0] - (-1.0)).abs() < 1e-10);
assert!((jac_data[3] - (-1.0)).abs() < 1e-10);
}
#[test]
fn test_iteration_matrix() {
let (device, client) = setup();
let j = Tensor::<CpuRuntime>::from_slice(&[1.0, 0.0, 0.0, 2.0], &[2, 2], &device);
let m = compute_iteration_matrix(&client, &j, 0.1, 1.0).unwrap();
let m_data: Vec<f64> = m.to_vec();
assert!((m_data[0] - 0.9).abs() < 1e-10);
assert!(m_data[1].abs() < 1e-10);
assert!(m_data[2].abs() < 1e-10);
assert!((m_data[3] - 0.8).abs() < 1e-10);
}
}