use crate::DType;
use numr::autograd::DualTensor;
use numr::error::Result;
use numr::ops::{ScalarOps, TensorOps};
use numr::runtime::{Runtime, RuntimeClient};
use numr::tensor::Tensor;
use crate::common::jacobian::jacobian_autograd;
pub fn compute_dae_jacobian<R, C, F>(
client: &C,
f: &F,
t: &Tensor<R>,
y: &Tensor<R>,
yp: &Tensor<R>,
coeff: f64,
) -> Result<Tensor<R>>
where
R: Runtime<DType = DType>,
C: TensorOps<R> + ScalarOps<R> + RuntimeClient<R>,
F: Fn(&DualTensor<R>, &DualTensor<R>, &DualTensor<R>, &C) -> Result<DualTensor<R>>,
{
let t_dual = DualTensor::new(t.clone(), None);
let yp_const = DualTensor::new(yp.clone(), None);
let y_const = DualTensor::new(y.clone(), None);
let j_y = jacobian_autograd(client, |y_dual, c| f(&t_dual, y_dual, &yp_const, c), y)?;
let t_dual_2 = DualTensor::new(t.clone(), None);
let j_yp = jacobian_autograd(client, |yp_dual, c| f(&t_dual_2, &y_const, yp_dual, c), yp)?;
let scaled_j_yp = client.mul_scalar(&j_yp, coeff)?;
client.add(&j_y, &scaled_j_yp)
}
pub fn eval_dae_primal<R, C, F>(
client: &C,
f: &F,
t: &Tensor<R>,
y: &Tensor<R>,
yp: &Tensor<R>,
) -> Result<Tensor<R>>
where
R: Runtime<DType = DType>,
C: TensorOps<R> + RuntimeClient<R>,
F: Fn(&DualTensor<R>, &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 yp_dual = DualTensor::new(yp.clone(), None);
let result = f(&t_dual, &y_dual, &yp_dual, client)?;
Ok(result.primal().clone())
}
#[cfg(test)]
mod tests {
use super::*;
use numr::autograd::dual_ops::{dual_mul_scalar, dual_sub};
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_dae_jacobian_simple() {
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 yp = Tensor::<CpuRuntime>::from_slice(&[2.0, 4.0], &[2], &device);
let f = |_t: &DualTensor<CpuRuntime>,
y: &DualTensor<CpuRuntime>,
yp: &DualTensor<CpuRuntime>,
c: &CpuClient| {
let two_y = dual_mul_scalar(y, 2.0, c)?;
dual_sub(yp, &two_y, c)
};
let jac = compute_dae_jacobian(&client, &f, &t, &y, &yp, 1.0).unwrap();
let jac_data: Vec<f64> = jac.to_vec();
assert!(
(jac_data[0] - (-1.0)).abs() < 1e-10,
"J[0,0] = {}",
jac_data[0]
);
assert!(jac_data[1].abs() < 1e-10, "J[0,1] = {}", jac_data[1]);
assert!(jac_data[2].abs() < 1e-10, "J[1,0] = {}", jac_data[2]);
assert!(
(jac_data[3] - (-1.0)).abs() < 1e-10,
"J[1,1] = {}",
jac_data[3]
);
}
#[test]
fn test_eval_dae_primal() {
let (device, client) = setup();
let t = Tensor::<CpuRuntime>::from_slice(&[0.0], &[1], &device);
let y = Tensor::<CpuRuntime>::from_slice(&[1.0, 3.0], &[2], &device);
let yp = Tensor::<CpuRuntime>::from_slice(&[2.0, 6.0], &[2], &device);
let f = |_t: &DualTensor<CpuRuntime>,
y: &DualTensor<CpuRuntime>,
yp: &DualTensor<CpuRuntime>,
c: &CpuClient| {
let two_y = dual_mul_scalar(y, 2.0, c)?;
dual_sub(yp, &two_y, c)
};
let residual = eval_dae_primal(&client, &f, &t, &y, &yp).unwrap();
let res_data: Vec<f64> = residual.to_vec();
assert!(res_data[0].abs() < 1e-10);
assert!(res_data[1].abs() < 1e-10);
}
#[test]
fn test_dae_jacobian_with_coeff() {
let (device, client) = setup();
let t = Tensor::<CpuRuntime>::from_slice(&[0.0], &[1], &device);
let y = Tensor::<CpuRuntime>::from_slice(&[1.0], &[1], &device);
let yp = Tensor::<CpuRuntime>::from_slice(&[1.0], &[1], &device);
let f = |_t: &DualTensor<CpuRuntime>,
y: &DualTensor<CpuRuntime>,
yp: &DualTensor<CpuRuntime>,
c: &CpuClient| { dual_sub(yp, y, c) };
let jac = compute_dae_jacobian(&client, &f, &t, &y, &yp, 2.0).unwrap();
let jac_data: Vec<f64> = jac.to_vec();
assert!((jac_data[0] - 1.0).abs() < 1e-10);
}
}