Skip to content

Add second non-mixed derivative to PartialDerivative and Dual2_64 to cache? #93

Closed

Description

@g-bauer

At the moment, we calculate all properties of a State that use second derivatives via HyperDual64. Second derivatives w.r.t a single property, i.e. non-mixed derivatives, can be calculated using Dual2_64 which are a bit more efficient.

Should we add Dual2_64 to the PartialDerivative enum and to the cache? E.g. we could do

// in state/mod.rs

#[derive(Clone, Copy, Eq, Hash, PartialEq, Debug)]
pub(crate) enum PartialDerivative {
    Zeroth,
    First(Derivative),
    Second(Derivative),
    SecondMixed(Derivative, Derivative),
    Third(Derivative),
}

/// Creates a [StateHD] taking the first and second (non-mixed) derivatives.
pub fn derive2(&self, derivative: Derivative) -> StateHD<Dual2_64> {
    let mut t = Dual2_64::from(self.reduced_temperature);
    let mut v = Dual2_64::from(self.reduced_volume);
    let mut n = self.reduced_moles.mapv(Dual2_64::from);
    match derivative {
        Derivative::DT => t = t.derive(),
        Derivative::DV => v = v.derive(),
        Derivative::DN(i) => n[i] = n[i].derive(),
    }
    StateHD::new(t, v, n)
}

/// Creates a [StateHD] taking the first and second mixed partial derivatives.
pub fn derive2_mixed(
    &self,
    derivative1: Derivative,
    derivative2: Derivative,
) -> StateHD<HyperDual64> {
    let mut t = HyperDual64::from(self.reduced_temperature);
    let mut v = HyperDual64::from(self.reduced_volume);
    let mut n = self.reduced_moles.mapv(HyperDual64::from);
    match derivative1 {
        Derivative::DT => t.eps1[0] = 1.0,
        Derivative::DV => v.eps1[0] = 1.0,
        Derivative::DN(i) => n[i].eps1[0] = 1.0,
    }
    match derivative2 {
        Derivative::DT => t.eps2[0] = 1.0,
        Derivative::DV => v.eps2[0] = 1.0,
        Derivative::DN(i) => n[i].eps2[0] = 1.0,
    }
    StateHD::new(t, v, n)
}

// in state/cache.rs

pub fn get_or_insert_with_d2_64<F: FnOnce() -> Dual2_64>(
    &mut self,
    derivative: Derivative,
    f: F,
) -> f64 {
    if let Some(&value) = self.map.get(&PartialDerivative::Second(derivative)) {
        self.hit += 1;
        value
    } else {
        self.miss += 1;
        let value = f();
        self.map.insert(PartialDerivative::Zeroth, value.re);
        self.map
            .insert(PartialDerivative::First(derivative), value.v1[0]);
        self.map
            .insert(PartialDerivative::Second(derivative), value.v2[0]);
        // also fill PartialDerivtave::SecondMixed(derivative, derivative)?
        value.v2[0]
    }
}

// in state/properties.rs

fn dp_dv_(&self, evaluate: Evaluate) -> QuantityScalar<U> {
        -self.get_or_compute_derivative(PartialDerivative::Second(DV), evaluate)
    }

fn dp_dt_(&self, evaluate: Evaluate) -> QuantityScalar<U> {
    -self.get_or_compute_derivative(PartialDerivative::SecondMixed(DV, DT), evaluate)
}

Background: This will speed up algorithms that use those non-mixed, second derivatives, e.g. the density iteration. First implementation shows speedup of ~9 % for density iteration and 6 % for tp_flash.

Activity

  1. added
    enhancementNew feature or request
    corerelated to features in feos-core
    on Dec 19, 2022
  2. prehner commented on Dec 20, 2022

    @prehner
    Contributor

    Sounds excellent! Do we need separate Second and SecondMixed entries for the cache specifically? Feels like we could just use the SecondMixed variant for that.

  3. g-bauer commented on Dec 20, 2022

    @g-bauer
    ContributorAuthor

    The cache stores derivatives as HashMap<PartialDerivative, f64>, so the Second variant is a possible key. But we could ignore that when filling the map, i.e. we can write

    fn get_or_insert_with_d2_64<F: FnOnce() -> Dual2_64>(
        &mut self,
        derivative: Derivative,
        f: F,
    ) -> f64 {
        if let Some(&value) = self.map.get(&PartialDerivative::SecondMixed(derivative, derivative)) {
            self.hit += 1;
            value
        } else {
            self.miss += 1;
            let value = f();
            self.map.insert(PartialDerivative::Zeroth, value.re);
            self.map
                .insert(PartialDerivative::First(derivative), value.v1[0]);
            self.map
                .insert(PartialDerivative::SecondMixed(derivative, derivative), value.v2[0]);
            value.v2[0]
        }
    }

    This just uses SecondMixed and would lead to a cache hit in case one computes the second derivative using the less efficient HyperDual64.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    corerelated to features in feos-coreenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions