-
-
Notifications
You must be signed in to change notification settings - Fork 449
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Add WeightedIndex::update_weights_relative #1403
Add WeightedIndex::update_weights_relative #1403
Conversation
…WeightedIndex relative to the current weights.
I can see two possible ways around this:
It comes down to how The old |
Hey @dhardy, thanks for the input. I prefer the first of the two options you proposed; I think the second sacrifices too much in the way of error handling. Since taking ownership of the passed weights is not really necessary I would aim for borrowing mutably; it might be interesting to the caller to see what the weights have been changed to after the fact. I think |
This would only work if we also changed the signature of We could also discuss alternative ways to solve this problem, as the |
But does this really matter? In many cases, errors are not designed to be handled, but just reported. Another half-baked idea would be to add the following:
The latter would make a copy of each weight before calling |
After working on an implementation of I'm starting to think it would be easier to just expose a method like the following and let the caller figure out the new weights themselves: pub fn weight_at(&self, index: usize) -> X
where
X: for<'a> ::core::ops::SubAssign<&'a X>
+ Clone
{
let mut weight = if index < self.cumulative_weights.len() {
self.cumulative_weights[index].clone()
} else {
self.total_weight.clone()
};
if index > 0 {
weight -= &self.cumulative_weights[index - 1];
}
weight
} This is the same as how the old weights are currently calculated inside the While we're at it, maybe exposing more of the inner workings of the distribution (immutably) would offer additional convenience at no extra risk, just in case it is useful: pub fn total_weight(&self) -> &X {
&self.total_weight
}
pub fn cumulative_weights(&self) -> &Vec<X> {
&self.cumulative_weights
} @dhardy what do you think about this? Is there reason not to do this? |
Reporting more inner details makes it harder to change the implementation later, but I don't see the implementation of
|
Closing this as #1420 was merged. |
CHANGELOG.md
entrySummary
This PR adds a new method
update_weights_relative
to theWeightedIndex
distribution, whose purpose is to update a subset of weights relative to the current values, following a provided mapping function.Motivation
Sometimes it is desirable to update a weight in a distribution relative to its current value, for instance subtracting one from the weight of a sampled index in order to achieve drawing without replacement. However, the
update_weights
method requires you to provide the new weights directly, and there is no external way to check the current weight at an index. This means that you have to track the current weights manually:In this code snippet,
remaining
has the type[X; len]
, whereX
is the element type of theWeightedIndex
andlen
is the number of weights in the index.Keeping this array around externally is tedious, and wastes space, since all the information we need is technically already stored within the index.
It would be nice to instead be able to do something like this, to lower the weight at
ordinal
by 1:But this only works with signed types - unsigned types would only ever be able to be increased. Furthermore, it seems like an opportunity here to allow not just for addition and subtraction, but for arbitrary mappings via a closure:
Here, we provide a closure describing in what way the provided values should be combined with the current weights: the relative value should be subtracted from the existing weight to yield the new weight.
This yields another potential issue: what if the arithmetic in this closure fails (overflow, underflow, etc)? We could have the closure return an
Option<X>
to account for failures. Then we can do something like this:...and the whole method will return a
WeightError
if any of these subtractions underflows.Details
The signature of the new
update_weights_relative
method is as follows:The generic type parameter
R
allows the provided values to not necessarily be the same type as the values in theWeightedIndex
, as long as the providedop
can combine the two into anX
.In the error checking phase,
update_weights_relative
follows the same logic asupdate_weights
, with a couple differences:>= zero
like inupdate_weights
(they do not even necessarily sharezero
's type), howeverop(old_weight, relative)
, the calculated new weight, must be>= zero
. If the calculated weight isNone
or< zero
,WeightError::InvalidWeight
is returned.Vec
is allocated to store these new weights until they can be applied. I would like to avoid this allocation but I don't see a way to do so -> suggestions welcome.After performing the necessary checks (and calculating the new absolute weights), the actual application of the new weights is identical to the
update_weights
method. Therefore, this section of theupdate_weights
method has been factored out, and is now called by both methods.Positive and negative test cases have been added for the new method (and for the existing
update_weights
method).This is my first contribution to this crate, I appreciate your feedback!