 # Implementing a combination of exact and approximate methods

I am trying to implement a certain probability distribution. This distribution has an exact and an approximate form. Generally this goes well:

``````struct MyDistribution {
approximation: Approximation,
}

enum Approximation {
Existing(ExistingDistribution),
Exact,
}

impl Continuous for MyDistribution {
fn pdf(&self, x: f64) -> f64 {
match self.approximation {
Approximation::Existing(distribution) => distribution.pdf(x),
Approximation::Exact => {
// Some calculations
todo!();
}
}
}
}
``````

However, in one of the trait implementations I want to use the default implementation for `Approximation::Exact` and a specific implementation from `ExistingApproximation` for `Approximation::Existing`. I don't think that is possible (implementing traits for variants) even though it should be fine if all the variants have an implementation. I could make a separate struct for the exact distribution and then wrap both in `MyDistribution` but that would duplicate a lot of code. Is there a better approach?

Can you give an example where the pattern you show here doesn't work? I'd normally just call the default implementation inside the `Approximation::Exact` match arm.

How would you call the default implementation? As far as I could tell, the following implementation of `ContinuousCDF::inverse_cdf` would recurse infinitely for `SignedRank { approximation: Approximation::Exact }`.

``````use statrs::distribution::{ContinuousCDF, Normal};

pub struct SignedRank {
approximation: Approximation,
}

enum Approximation {
Normal(Normal),
Exact,
}

impl ContinuousCDF<f64, f64> for SignedRank {
fn cdf(&self, x: f64) -> f64 {
match self.approximation {
Approximation::Normal(normal) => 2.0 * normal.cdf(x),
Approximation::Exact => /* ... */ 0.0,
}
}

fn inverse_cdf(&self, x: f64) -> f64 {
match self.approximation {
Approximation::Normal(normal) => normal.inverse_cdf(x),
Approximation::Exact => ContinuousCDF::inverse_cdf(self, x),
}
}
}
``````

I'm not familiar with the `statsrs` package, but I don't understand how this could work with any implementation: Where is the raw data coming from that is necessary to calculate the CDF in the `Exact` case?

I removed that for the example to keep it short, sorry. `SignedRank` has an additional field `n: usize` which is sufficient:

``````let r = x.ceil() as usize;

let mut sum = 1;

for n in 1..=self.n {
let n_choose_2 = binomial(n as u64, 2) as usize;
if n_choose_2 > r { continue }
for i in n..=(r - n_choose_2) {
sum += partitions(i, n, self.n - n + 1);
}
}

sum as f64 / 2_f64.powi(self.n as i32 - 1)
``````

Full file on GitHub

I see. In this case, I'd probably consider putting the enum at the top level:

``````enum SignedRank {
Normal(Normal),
Exact(ExactSignedRank)
}

struct ExactSignedRank(usize);
``````

Then, you can defer to the `ContinuousCDF` implementation of the variant's argument in both cases.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.