-
-
Notifications
You must be signed in to change notification settings - Fork 10
MultiDistribution #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
I put a I am not sure if I want a different name, because it clashed with the one in |
Sorry, it should be
I'm not really sure of the answer to that. Any of these can work (from the POV of a dependency):
|
I was talking about the naming of I guess I also prefer the last of the 3 options for the name of the trait. |
Also, should we do this in a |
This shouldn't be an issue unless a type implements both Except possibly a symmetric distribution like |
I was thinking about both having |
The point of using Which leads to another point: we may want a So:
That requires |
We should decide if we want to keep the possibility to have multidim sampling without allocations or not. If we do not need it, we can ditch the const generics and have less code and then your proposed Trait would make a lot of sense. I am still leaning toward keeping it, because I had a usecase where Multinomial samples where extremely time critical and it helps to save the allocations, especially in multithread where there is synchronization with malloc. But this might also be a niche usecase. (I would only sample once per Multinomial) Edit: Your Trait is also still implementable for a const generic version and you can avoid all allocations. So I would go with this approach. |
I thought we did decide to drop the const-generics approach for Your use-case sounds fairly specific. Maybe there are further optimisations available when sampling only once. |
Would it be possible to have a non-const generic impl<F: Float> Dirichlet<F> {
pub fn new(alpha: Vec<F>) -> Result<Dirichlet<F>, Error>;
pub fn sample<R: Rng>(&self, rng: &mut R, output: &mut Vec<F>); // Users can re-use this vector to avoid allocations
pub fn into_vec(self) -> Vec<F>; // Recovers the memory passed in with `new()`
} This still requires the For |
In the case of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments on comment style below.
More significantly, the sample
method now looks identical to Distribution::sample
, hence your idea to use that trait likely makes more sense: we can automatically impl Distribution<Vec<T>> where T: Default
.
This would also allow an explicit impl of Distribution<[T; N]>
where appropriate (e.g. your mentioned const-generic Multinomial
).
But, at this point, do we still want the MultiDistribution
trait at all?
//! We provide a trait `MultiDistribution` which allows to sample from a multi-dimensional distribution without extra allocations. | ||
//! All multi-dimensional distributions implement `MultiDistribution` instead of the `Distribution` trait. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We generally wrap comments at 80 chars width (sometimes up to 100 if the line already has a large indent).
The wording could be a little better, e.g.
The
MultiDistribution
trait allows sampling a multi-dimensional distribution to a pre-allocated buffer or to a new [Vec
].
/// This trait allows to sample from a multi-dimensional distribution without extra allocations. | ||
/// For convenience it also provides a `sample` method which returns the result as a `Vec`. | ||
pub trait MultiDistribution<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Items have a short one-line description, with additional details in new paragraphs.
My current favorite would be something like this:
The distributions would implement Unfortunately this does not work because of orphan rules. It also does not feel right to define the We could just implement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can go with this trait design.
fn sample_to_buf<R: Rng + ?Sized>(&self, rng: &mut R, output: &mut [F]) { | ||
let mut sum = F::zero(); | ||
|
||
for (s, g) in samples.iter_mut().zip(self.samplers.iter()) { | ||
for (s, g) in output.iter_mut().zip(self.samplers.iter()) { | ||
*s = g.sample(rng); | ||
sum = sum + *s; | ||
} | ||
let invacc = F::one() / sum; | ||
for s in samples.iter_mut() { | ||
for s in output.iter_mut() { | ||
*s = *s * invacc; | ||
} | ||
samples | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the length of output
affects behaviour, I would expect an assert
on it.
fn sample_to_buf<R: Rng + ?Sized>(&self, rng: &mut R, output: &mut [F]) { | ||
let mut acc = F::one(); | ||
|
||
for (s, beta) in samples.iter_mut().zip(self.samplers.iter()) { | ||
for (s, beta) in output.iter_mut().zip(self.samplers.iter()) { | ||
let beta_sample = beta.sample(rng); | ||
*s = acc * beta_sample; | ||
acc = acc * (F::one() - beta_sample); | ||
} | ||
samples[N - 1] = acc; | ||
samples | ||
output[N - 1] = acc; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one too.
/// returns the length of one sample (dimension of the distribution) | ||
fn sample_len(&self) -> usize; | ||
/// samples from the distribution and writes the result to `buf` | ||
fn sample_to_buf<R: Rng + ?Sized>(&self, rng: &mut R, buf: &mut [T]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method should be called sample_to_slice
in my opinion. Or perhaps just sample_to
.
CHANGELOG.md
entrySummary
Some code related to #16