# Compare and merge sets from unstructured variables for automatic differentiation

I have developed an automatic differentiation module for my software. Usually AD comes in two forms; forward mode or reverse mode and very clever approaches, beyond me, might mix both. Typically the fastest of these rely on the problems being pre-structured and variables organised consistently to leverage ops.

However, my problems do not have structured variables and they may be dynamically created by users, which means that every mathematical operation that ever occurs always requires a vars check to ensure that vectors are aligned to correctly capture derivatives. Hence this check needs to be as efficient as possible. I have tried to use an Arc pointer since pointer comparisons are almost instantaneous.

Is this is a sensible and efficient approach? In empirical tests, it seems to work quite well.


/// Struct for defining a dual number data type supporting first order derivatives.
#[pyclass]
#[derive(Clone, Default, Debug)]
pub struct Dual {
real: f64,
vars: Arc<IndexSet<String>>,
dual: Array1<f64>,
}

/// Enum defining the vars state of two dual number type structs, a LHS relative to a RHS.
#[derive(Clone, Debug, PartialEq)]
EquivByArc,  // Duals share an Arc ptr to their Vars
EquivByVal,  // Duals share the same vars in the same order but no Arc ptr
Superset,    // The Dual vars contains all of the queried values and is larger set
Subset,      // The Dual vars is contained in the queried values and is smaller set
Difference,  // The Dual vars and the queried set contain different values.
}

/// A trait to order and manage the variables of the manifold associated with a dual number.
pub trait Vars where Self: Clone {
/// Get a reference to the Arc pointer for the IndexSet containing the struct's variables.
fn vars(&self) -> &Arc<IndexSet<String>>;

/// Create a new dual number with vars aligned with given new Arc pointer.
fn to_new_vars(&self, arc_vars: &Arc<IndexSet<String>>, state: Option<VarsState>) -> Self;

/// Compare the vars on a Dual with a given Arc pointer.
fn vars_cmp(&self, arc_vars: &Arc<IndexSet<String>>) -> VarsState {
if Arc::ptr_eq(self.vars(), arc_vars) {
} else if self.vars().len() == arc_vars.len()
&& self.vars().iter().zip(arc_vars.iter()).all(|(a, b)| a == b) {
} else if self.vars().len() >= arc_vars.len()
&& arc_vars.iter().all(|var| self.vars().contains(var)) {
} else if self.vars().len() < arc_vars.len()
&& self.vars().iter().all(|var| arc_vars.contains(var)) {
} else {
}
}

/// Construct a tuple of 2 Self types whose vars are linked by an Arc pointer.
fn to_union_vars(&self, other: &Self, state: Option<VarsState>) -> (Self, Self) where Self: Sized {
let state_ = state.unwrap_or_else(|| self.vars_cmp(other.vars()));
match state_ {
}
}

/// Construct a tuple of 2 Self types whose vars are linked by the explicit union
fn to_combined_vars(&self, other: &Self) -> (Self, Self) where Self: Sized {
let comb_vars = Arc::new(IndexSet::from_iter(
self.vars().union(&other.vars()).map(|x| x.clone()),
));
/// Compare if two Dual structs share the same varsby Arc pointer equivalence.