1
\$\begingroup\$

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)]
pub enum VarsState {
    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) {
            VarsState::EquivByArc
        } else if self.vars().len() == arc_vars.len()
            && self.vars().iter().zip(arc_vars.iter()).all(|(a, b)| a == b) {
            VarsState::EquivByVal
        } else if self.vars().len() >= arc_vars.len()
            && arc_vars.iter().all(|var| self.vars().contains(var)) {
            VarsState::Superset
        } else if self.vars().len() < arc_vars.len()
            && self.vars().iter().all(|var| arc_vars.contains(var)) {
            VarsState::Subset
        } else {
            VarsState::Difference
        }
    }

    /// 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_ {
            VarsState::EquivByArc => (self.clone(), other.clone()),
            VarsState::EquivByVal => (self.clone(), other.to_new_vars(self.vars(), Some(state_))),
            VarsState::Superset => (self.clone(), other.to_new_vars(self.vars(), Some(VarsState::Subset))),
            VarsState::Subset => (self.to_new_vars(other.vars(), Some(state_)), other.clone()),
            VarsState::Difference => self.to_combined_vars(other),
        }
    }

    /// 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()),
        ));
        (self.to_new_vars(&comb_vars, Some(VarsState::Difference)),
         other.to_new_vars(&comb_vars, Some(VarsState::Difference)))
    }

    /// Compare if two `Dual` structs share the same `vars`by Arc pointer equivalence.
    fn ptr_eq(&self, other: &Self) -> bool {
        Arc::ptr_eq(self.vars(), other.vars())
    }
}
\$\endgroup\$
1
  • 1
    \$\begingroup\$ I changed the title so that it describes what the code does per site goals: "State what your code does in your title, not your main concerns about it.". Please check that I haven't misrepresented your code, and correct it if I have. \$\endgroup\$ Commented May 22 at 15:42

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.