use std::fmt::Debug;
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::Float;
use std::collections::HashMap;
use crate::error::{OptimError, Result};
#[derive(Debug, Clone)]
pub struct DualNumber<T: Float + Debug + Send + Sync + 'static> {
pub value: T,
pub tangent: T,
}
#[derive(Debug, Clone)]
pub struct VectorDual<T: Float + Debug + Send + Sync + 'static> {
pub value: Array1<T>,
pub tangent: Array1<T>,
}
pub struct ForwardModeEngine<T: Float + Debug + Default + Clone + Send + Sync + std::iter::Sum + 'static> {
tape: Vec<ForwardOperation<T>>,
variables: HashMap<String, usize>,
seed_vectors: Vec<Array1<T>>,
higher_order: bool,
}
#[derive(Debug, Clone)]
struct ForwardOperation<T: Float + Debug + Send + Sync + 'static> {
optype: ForwardOpType,
inputs: Vec<usize>,
output: usize,
metadata: ForwardOpMetadata<T>,
}
#[derive(Debug, Clone)]
enum ForwardOpType {
Variable,
Constant,
Add,
Subtract,
Multiply,
Divide,
Power,
Exp,
Log,
Sin,
Cos,
Tanh,
Sigmoid,
ReLU,
MatMul,
Dot,
Sum,
Mean,
Norm,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
struct ForwardOpMetadata<T: Float + Debug + Send + Sync + 'static> {
partials: Vec<T>,
shape: Vec<usize>,
data: ForwardOpData<T>,
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
enum ForwardOpData<T: Float + Debug + Send + Sync + 'static> {
None,
ConstantValue(T),
PowerExponent(T),
MatMulDims { m: usize, n: usize, k: usize },
ReductionAxis(usize),
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> DualNumber<T> {
pub fn new(value: T, tangent: T) -> Self {
Self { value, tangent }
}
pub fn constant(value: T) -> Self {
Self::new(value, T::zero())
}
pub fn variable(value: T) -> Self {
Self::new(value, T::one())
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> VectorDual<T> {
pub fn new(value: Array1<T>, tangent: Array1<T>) -> Self {
Self { value, tangent }
}
pub fn constant(value: Array1<T>) -> Self {
let tangent = Array1::zeros(value.len());
Self::new(value, tangent)
}
pub fn variable(value: Array1<T>, direction: usize) -> Self {
let mut tangent = Array1::zeros(value.len());
if direction < tangent.len() {
tangent[direction] = T::one();
}
Self::new(value, tangent)
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + std::iter::Sum + 'static> ForwardModeEngine<T> {
pub fn new() -> Self {
Self {
tape: Vec::new(),
variables: HashMap::new(),
seed_vectors: Vec::new(),
higher_order: false,
}
}
pub fn enable_higher_order(&mut self, enabled: bool) {
self.higher_order = enabled;
}
pub fn set_seed_vectors(&mut self, seeds: Vec<Array1<T>>) {
self.seed_vectors = seeds;
}
pub fn create_variable(&mut self, name: &str, value: Array1<T>) -> usize {
let var_id = self.tape.len();
let op = ForwardOperation {
optype: ForwardOpType::Variable,
inputs: Vec::new(),
output: var_id,
metadata: ForwardOpMetadata {
partials: vec![T::one()],
shape: value.shape().to_vec(),
data: ForwardOpData::None,
},
};
self.tape.push(op);
self.variables.insert(name.to_string(), var_id);
var_id
}
pub fn create_constant(&mut self, value: Array1<T>) -> usize {
let const_id = self.tape.len();
let op = ForwardOperation {
optype: ForwardOpType::Constant,
inputs: Vec::new(),
output: const_id,
metadata: ForwardOpMetadata {
partials: vec![T::zero()],
shape: value.shape().to_vec(),
data: ForwardOpData::ConstantValue(value[0]),
},
};
self.tape.push(op);
const_id
}
pub fn add(&mut self, lhs: usize, rhs: usize) -> Result<usize> {
self.binary_op(ForwardOpType::Add, lhs, rhs)
}
pub fn subtract(&mut self, lhs: usize, rhs: usize) -> Result<usize> {
self.binary_op(ForwardOpType::Subtract, lhs, rhs)
}
pub fn multiply(&mut self, lhs: usize, rhs: usize) -> Result<usize> {
self.binary_op(ForwardOpType::Multiply, lhs, rhs)
}
pub fn divide(&mut self, lhs: usize, rhs: usize) -> Result<usize> {
self.binary_op(ForwardOpType::Divide, lhs, rhs)
}
pub fn power(&mut self, base: usize, exponent: T) -> Result<usize> {
let output_id = self.tape.len();
let op = ForwardOperation {
optype: ForwardOpType::Power,
inputs: vec![base],
output: output_id,
metadata: ForwardOpMetadata {
partials: vec![T::zero()], shape: vec![], data: ForwardOpData::PowerExponent(exponent),
},
};
self.tape.push(op);
Ok(output_id)
}
pub fn exp(&mut self, input: usize) -> Result<usize> {
self.unary_op(ForwardOpType::Exp, input)
}
pub fn log(&mut self, input: usize) -> Result<usize> {
self.unary_op(ForwardOpType::Log, input)
}
pub fn sin(&mut self, input: usize) -> Result<usize> {
self.unary_op(ForwardOpType::Sin, input)
}
pub fn cos(&mut self, input: usize) -> Result<usize> {
self.unary_op(ForwardOpType::Cos, input)
}
pub fn tanh(&mut self, input: usize) -> Result<usize> {
self.unary_op(ForwardOpType::Tanh, input)
}
pub fn sigmoid(&mut self, input: usize) -> Result<usize> {
self.unary_op(ForwardOpType::Sigmoid, input)
}
pub fn relu(&mut self, input: usize) -> Result<usize> {
self.unary_op(ForwardOpType::ReLU, input)
}
pub fn matmul(&mut self, lhs: usize, rhs: usize, dims: (usize, usize, usize)) -> Result<usize> {
let output_id = self.tape.len();
let op = ForwardOperation {
optype: ForwardOpType::MatMul,
inputs: vec![lhs, rhs],
output: output_id,
metadata: ForwardOpMetadata {
partials: vec![T::zero(), T::zero()],
shape: vec![dims.0, dims.2],
data: ForwardOpData::MatMulDims {
m: dims.0,
n: dims.1,
k: dims.2,
},
},
};
self.tape.push(op);
Ok(output_id)
}
pub fn dot(&mut self, lhs: usize, rhs: usize) -> Result<usize> {
self.binary_op(ForwardOpType::Dot, lhs, rhs)
}
pub fn sum(&mut self, input: usize, axis: Option<usize>) -> Result<usize> {
let output_id = self.tape.len();
let op = ForwardOperation {
optype: ForwardOpType::Sum,
inputs: vec![input],
output: output_id,
metadata: ForwardOpMetadata {
partials: vec![T::zero()],
shape: vec![], data: ForwardOpData::ReductionAxis(axis.unwrap_or(0)),
},
};
self.tape.push(op);
Ok(output_id)
}
pub fn mean(&mut self, input: usize, axis: Option<usize>) -> Result<usize> {
let output_id = self.tape.len();
let op = ForwardOperation {
optype: ForwardOpType::Mean,
inputs: vec![input],
output: output_id,
metadata: ForwardOpMetadata {
partials: vec![T::zero()],
shape: vec![], data: ForwardOpData::ReductionAxis(axis.unwrap_or(0)),
},
};
self.tape.push(op);
Ok(output_id)
}
pub fn norm(&mut self, input: usize) -> Result<usize> {
self.unary_op(ForwardOpType::Norm, input)
}
pub fn forward_pass(
&self,
inputs: &HashMap<String, Array1<T>>,
seed_direction: &Array1<T>,
) -> Result<Vec<VectorDual<T>>> {
let mut values = Vec::with_capacity(self.tape.len());
for op in &self.tape {
match op.optype {
ForwardOpType::Variable => {
let var_name = self
.variables
.iter()
.find(|(_, &id)| id == op.output)
.map(|(name_, _)| name_.clone())
.ok_or_else(|| {
OptimError::InvalidConfig("Variable not found".to_string())
})?;
let value = inputs
.get(&var_name)
.ok_or_else(|| {
OptimError::InvalidConfig("Input value not provided".to_string())
})?
.clone();
let tangent = if op.output < seed_direction.len() {
Array1::from_elem(value.len(), seed_direction[op.output])
} else {
Array1::zeros(value.len())
};
values.push(VectorDual::new(value, tangent));
}
ForwardOpType::Constant => {
if let ForwardOpData::ConstantValue(val) = op.metadata.data {
let value = Array1::from_elem(1, val);
let tangent = Array1::zeros(1);
values.push(VectorDual::new(value, tangent));
} else {
return Err(OptimError::InvalidConfig(
"Invalid constant data".to_string(),
));
}
}
_ => {
let result = self.compute_forward_operation(op, &values)?;
values.push(result);
}
}
}
Ok(values)
}
pub fn jacobian_vector_product(
&self,
inputs: &HashMap<String, Array1<T>>,
output_id: usize,
direction: &Array1<T>,
) -> Result<Array1<T>> {
let results = self.forward_pass(inputs, direction)?;
if output_id >= results.len() {
return Err(OptimError::InvalidConfig("Invalid output ID".to_string()));
}
Ok(results[output_id].tangent.clone())
}
pub fn jacobian_matrix(
&self,
inputs: &HashMap<String, Array1<T>>,
output_id: usize,
input_size: usize,
) -> Result<Array2<T>> {
let mut jacobian = Array2::zeros((inputs.len(), input_size));
for i in 0..input_size {
let mut direction = Array1::zeros(input_size);
direction[i] = T::one();
let jvp = self.jacobian_vector_product(inputs, output_id, &direction)?;
for (j, &val) in jvp.iter().enumerate() {
if j < jacobian.nrows() {
jacobian[[j, i]] = val;
}
}
}
Ok(jacobian)
}
fn binary_op(&mut self, optype: ForwardOpType, lhs: usize, rhs: usize) -> Result<usize> {
let output_id = self.tape.len();
let op = ForwardOperation {
optype,
inputs: vec![lhs, rhs],
output: output_id,
metadata: ForwardOpMetadata {
partials: vec![T::zero(), T::zero()],
shape: vec![], data: ForwardOpData::None,
},
};
self.tape.push(op);
Ok(output_id)
}
fn unary_op(&mut self, optype: ForwardOpType, input: usize) -> Result<usize> {
let output_id = self.tape.len();
let op = ForwardOperation {
optype,
inputs: vec![input],
output: output_id,
metadata: ForwardOpMetadata {
partials: vec![T::zero()],
shape: vec![], data: ForwardOpData::None,
},
};
self.tape.push(op);
Ok(output_id)
}
fn compute_forward_operation(
&self,
op: &ForwardOperation<T>,
values: &[VectorDual<T>],
) -> Result<VectorDual<T>> {
match op.optype {
ForwardOpType::Add => {
let lhs = &values[op.inputs[0]];
let rhs = &values[op.inputs[1]];
let value = &lhs.value + &rhs.value;
let tangent = &lhs.tangent + &rhs.tangent;
Ok(VectorDual::new(value, tangent))
}
ForwardOpType::Subtract => {
let lhs = &values[op.inputs[0]];
let rhs = &values[op.inputs[1]];
let value = &lhs.value - &rhs.value;
let tangent = &lhs.tangent - &rhs.tangent;
Ok(VectorDual::new(value, tangent))
}
ForwardOpType::Multiply => {
let lhs = &values[op.inputs[0]];
let rhs = &values[op.inputs[1]];
let value = &lhs.value * &rhs.value;
let tangent = &lhs.tangent * &rhs.value + &lhs.value * &rhs.tangent;
Ok(VectorDual::new(value, tangent))
}
ForwardOpType::Divide => {
let lhs = &values[op.inputs[0]];
let rhs = &values[op.inputs[1]];
let value = &lhs.value / &rhs.value;
let numerator = &lhs.tangent * &rhs.value - &lhs.value * &rhs.tangent;
let denominator = &rhs.value * &rhs.value;
let tangent = numerator / denominator;
Ok(VectorDual::new(value, tangent))
}
ForwardOpType::Power => {
let base = &values[op.inputs[0]];
if let ForwardOpData::PowerExponent(exp) = op.metadata.data {
let value = base.value.mapv(|x| x.powf(exp));
let derivative = base.value.mapv(|x| exp * x.powf(exp - T::one()));
let tangent = derivative * &base.tangent;
Ok(VectorDual::new(value, tangent))
} else {
Err(OptimError::InvalidConfig(
"Invalid power operation".to_string(),
))
}
}
ForwardOpType::Exp => {
let input = &values[op.inputs[0]];
let value = input.value.mapv(|x| x.exp());
let tangent = &value * &input.tangent;
Ok(VectorDual::new(value, tangent))
}
ForwardOpType::Log => {
let input = &values[op.inputs[0]];
let value = input.value.mapv(|x| x.ln());
let tangent = &input.tangent / &input.value;
Ok(VectorDual::new(value, tangent))
}
ForwardOpType::Sin => {
let input = &values[op.inputs[0]];
let value = input.value.mapv(|x| x.sin());
let derivative = input.value.mapv(|x| x.cos());
let tangent = derivative * &input.tangent;
Ok(VectorDual::new(value, tangent))
}
ForwardOpType::Cos => {
let input = &values[op.inputs[0]];
let value = input.value.mapv(|x| x.cos());
let derivative = input.value.mapv(|x| -x.sin());
let tangent = derivative * &input.tangent;
Ok(VectorDual::new(value, tangent))
}
ForwardOpType::Tanh => {
let input = &values[op.inputs[0]];
let value = input.value.mapv(|x| x.tanh());
let derivative = value.mapv(|y| T::one() - y * y);
let tangent = derivative * &input.tangent;
Ok(VectorDual::new(value, tangent))
}
ForwardOpType::Sigmoid => {
let input = &values[op.inputs[0]];
let value = input.value.mapv(|x| T::one() / (T::one() + (-x).exp()));
let derivative = value.mapv(|y| y * (T::one() - y));
let tangent = derivative * &input.tangent;
Ok(VectorDual::new(value, tangent))
}
ForwardOpType::ReLU => {
let input = &values[op.inputs[0]];
let value = input
.value
.mapv(|x| if x > T::zero() { x } else { T::zero() });
let derivative = input
.value
.mapv(|x| if x > T::zero() { T::one() } else { T::zero() });
let tangent = derivative * &input.tangent;
Ok(VectorDual::new(value, tangent))
}
ForwardOpType::Dot => {
let lhs = &values[op.inputs[0]];
let rhs = &values[op.inputs[1]];
let value = Array1::from_elem(1, lhs.value.dot(&rhs.value));
let tangent =
Array1::from_elem(1, lhs.tangent.dot(&rhs.value) + lhs.value.dot(&rhs.tangent));
Ok(VectorDual::new(value, tangent))
}
ForwardOpType::Sum => {
let input = &values[op.inputs[0]];
let value = Array1::from_elem(1, input.value.sum());
let tangent = Array1::from_elem(1, input.tangent.sum());
Ok(VectorDual::new(value, tangent))
}
ForwardOpType::Mean => {
let input = &values[op.inputs[0]];
let n = T::from(input.value.len()).expect("unwrap failed");
let value = Array1::from_elem(1, input.value.sum() / n);
let tangent = Array1::from_elem(1, input.tangent.sum() / n);
Ok(VectorDual::new(value, tangent))
}
ForwardOpType::Norm => {
let input = &values[op.inputs[0]];
let norm = input.value.iter().map(|&x| x * x).sum::<T>().sqrt();
let value = Array1::from_elem(1, norm);
let tangent = if norm > T::zero() {
let dot_product = input.value.dot(&input.tangent);
Array1::from_elem(1, dot_product / norm)
} else {
Array1::zeros(1)
};
Ok(VectorDual::new(value, tangent))
}
_ => Err(OptimError::InvalidConfig(
"Unsupported operation".to_string(),
)),
}
}
pub fn get_graph_stats(&self) -> ForwardModeStats {
ForwardModeStats {
num_operations: self.tape.len(),
num_variables: self.variables.len(),
memory_usage_estimate: self.estimate_memory_usage(),
max_depth: self.compute_max_depth(),
}
}
fn estimate_memory_usage(&self) -> usize {
self.tape.len() * std::mem::size_of::<ForwardOperation<T>>()
}
fn compute_max_depth(&self) -> usize {
self.tape.len()
}
}
#[derive(Debug, Clone)]
pub struct ForwardModeStats {
pub num_operations: usize,
pub num_variables: usize,
pub memory_usage_estimate: usize,
pub max_depth: usize,
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> std::ops::Add for DualNumber<T> {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
value: self.value + rhs.value,
tangent: self.tangent + rhs.tangent,
}
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> std::ops::Sub for DualNumber<T> {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self {
value: self.value - rhs.value,
tangent: self.tangent - rhs.tangent,
}
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> std::ops::Mul for DualNumber<T> {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
Self {
value: self.value * rhs.value,
tangent: self.tangent * rhs.value + self.value * rhs.tangent,
}
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> std::ops::Div for DualNumber<T> {
type Output = Self;
fn div(self, rhs: Self) -> Self {
Self {
value: self.value / rhs.value,
tangent: (self.tangent * rhs.value - self.value * rhs.tangent)
/ (rhs.value * rhs.value),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dual_number_creation() {
let dual = DualNumber::new(2.0, 1.0);
assert_eq!(dual.value, 2.0);
assert_eq!(dual.tangent, 1.0);
}
#[test]
fn test_dual_number_arithmetic() {
let x = DualNumber::new(3.0, 1.0);
let y = DualNumber::new(2.0, 0.0);
let sum = x.clone() + y.clone();
assert_eq!(sum.value, 5.0);
assert_eq!(sum.tangent, 1.0);
let product = x * y;
assert_eq!(product.value, 6.0);
assert_eq!(product.tangent, 2.0);
}
#[test]
fn test_forward_mode_engine() {
let mut engine = ForwardModeEngine::<f64>::new();
let x_val = Array1::from_vec(vec![2.0]);
let x_id = engine.create_variable("x", x_val.clone());
let y_val = Array1::from_vec(vec![3.0]);
let y_id = engine.create_variable("y", y_val.clone());
let sum_id = engine.add(x_id, y_id).expect("unwrap failed");
let mut inputs = HashMap::new();
inputs.insert("x".to_string(), x_val);
inputs.insert("y".to_string(), y_val);
let direction = Array1::from_vec(vec![1.0, 0.0]);
let results = engine.forward_pass(&inputs, &direction).expect("unwrap failed");
assert!(results.len() > sum_id);
assert_eq!(results[sum_id].value[0], 5.0);
}
#[test]
fn test_vector_dual_operations() {
let value1 = Array1::from_vec(vec![1.0, 2.0, 3.0]);
let tangent1 = Array1::from_vec(vec![1.0, 0.0, 0.0]);
let dual1 = VectorDual::new(value1, tangent1);
assert_eq!(dual1.value.len(), 3);
assert_eq!(dual1.tangent.len(), 3);
assert_eq!(dual1.tangent[0], 1.0);
}
}