-
Notifications
You must be signed in to change notification settings - Fork 0
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
Update the code for n-d #4
Conversation
Now the code should work for any number of dimensions. It would be nice to be able to somehow join the |
If I do |
I guess it's disambiguating the kind of operation, or something like. Do we have a reference to the specific lint? |
It points to this https://rust-lang.github.io/rust-clippy/master/index.html#/suboptimal_flops But if it is the "rustacean" way to do it I can learn to live with it x) |
I think it's possible the compiler will not do it or will only do it under very specific circumstances because it would require reordering floating-point operations, which usually isn't done because FLOPS aren't associative. On Intel CPUs |
But then clippy is taking a way too aggressive stance! |
Well, remind yourself where its name comes from 😄. But if it annoys you, just disable the lint. |
Some of the lints I will remove/ignore because I feel they are a bit extreme. But this particular one I want to know what you guys usually do. |
To be fair, it seems like I've never done enough math in a single line in Rust to invoke this lint. I would try to benchmark once just for the sake of it (since benchmarks are already in place), and then you could discard in favor of simplicity. |
No, I'm sure it's clever. But a bit extreme. I the benchmark the differences are within the variance. |
Ok, then you could forget that lint. I bet it won't affect much even in the 2D case. |
I need to configure the silencing of specific lints because my OCD won't allow me to leave a warning alive :_ |
Isn't this one? e.g. I guess the proper one to be: |
Moreover, it seems you could also do it per-function:
I guess it should work as an attribute: #[deny(clippy::suboptimal_flops)]
fn interpolate(&self, query: f64) -> Result<f64, InterpolationError> {
...
} But I haven't tried myself, so I'm not sure. |
I'll play around with it next week. For the rest, if you are happy with the "generalization" in this branch, next week* I'll fix/silence clippy warnings and implement the 2D version. *I intend on doing this on the train on Thursday more specifically. I might even pass by the department since I'll have a 3 or 4 hours train-change in Milan :) |
pub fn closest_below<const N: usize>( | ||
&self, | ||
input_query: &[f64], | ||
) -> Result<[usize; N], InterpolationError> { |
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.
Here, I believe you'd like to use something like D::NDIM
.
Unfortunately, D::NDIM: Option<usize>
, so you'd need something like .unwrap()
, but executed at compile time.
https://docs.rs/ndarray/0.15.6/ndarray/trait.Dimension.html#associatedconstant.NDIM
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.
A simpler alternative might be to split the bulk of the function, and then implement thin wrappers around (one per Dimension
).
Even the implementors of Dimension
itself are just 7 (+ 1).
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.
Otherwise, you should be able to use a const function to unwrap it, defining an associated constant, and try to use the associated constant in the signature.
https://users.rust-lang.org/t/compile-time-const-unwrapping/51619
EDIT: Unfortunately, const panic is not stabilized, because of an issue with Rust 2021 rust-lang/rust#85194, maybe you could check if const pattern matching is allowed
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.
A const function to unwrap IxN at compile time you mean?
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.
Indeed, in order to turn it into a usize
(if you can not panic
, you can always turn IxDyn
into a 0
, it's useless anyhow, and it should not happen, so I wouldn't care that much).
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 this cannot be done. Or at least I cannot find a way of passing the D
to the const function:
generic parameters may not be used in const operations
type parameter may not be used in const expressions
and in any case, if I use a function that could depend on one of the parameters:
const parameters may only be used as standalone arguments
Not sure whether that means there's no way of doing what I want to do (without creating a path per dimension, at that point I rather leave the N
)
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'd really like to solve this issue. But if it's not simple, we could postpone.
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'd like to as well, but I haven't been able to...
I've decided to remove |
@scarlehoff whenever you're ready, feel free to merge |
@alecandido have a look when you have time to the last commit The motivation for the After this I'll start with the 2D interpolation, I promise :P |
impl Grid<Ix1> { | ||
/// A grid slice is always 1-Dimensional | ||
#[derive(Debug)] | ||
pub struct GridSlice<'a> { |
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.
Eventually, you started playing with lifetimes...
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 don't understand why the compiler is not able to deduce all of them though.
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.
There are rules to infer references lifetimes for methods, but even those are limited (to simplest but also most common cases, so they are very useful). You definitely want to infer as much as possible, but consistency is hard to impose when you have many rules (I recently read about attempts to introduce linear types, and they are a mess...).
I'm not sure, but I believe the reason you need to make them explicit should be that there is an alternative way of doing, e.g. having more lifetimes and some of them explicitly specified (like generics), or something like that. Maybe?
Maybe the optimization boils down to fewer allocations. Could it be? |
If you are happy with the
If it is due to that I cannot see where the extra allocations happen. |
I'm going to rereview quickly, but I guess we can merge.
I assumed you were using |
For the 1D case this is (almost) literally the case, but the only difference is that now the compiler has a bit more information: "From here onwards nothing will be touched". For the 2D case GridSlice is a 1D slice of Grid. |
pub values: Array1<f64>, | ||
pub struct Grid<D: Dimension> { | ||
/// Arrays with the input vectors (x_i) | ||
pub xgrid: Vec<Vec<f64>>, |
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.
Here an even nicer option (for later on) would be something like Vec<[f64; N]>
, with N: usize
determined from D
.
But it is again the same problem we already discussed before...
#[derive(Debug)] | ||
pub struct GridSlice<'a> { | ||
/// A reference to one of the input vectors of the grid | ||
pub x: &'a Vec<f64>, |
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.
Are you sure you need a reference to a vector, and not a slice?
(with slice I mean &'a [f64]
, as per standard library)
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.
Depends on what you mean with “need”, that might also work (and be more correct?), but x is a reference to one of the quantities in the xgrid which are vectors hence the choice.
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.
If you need to use vector-specific operations, then yes, you need the reference to a vector.
But if you need to reference just a collection of points, the slice is more general, and it relies less on the underlying structure (while still be plenty of possible methods, https://doc.rust-lang.org/std/primitive.slice.html).
However, if it has to be one entire element (the full x
coordinates) maybe it makes sense to keep as it is. I'd suggest instead to give it a name:
type XPoint = Vec<f64>;
(if you're able to find a better name, you're welcome). It's just an alias, for greater clarity, but here a newtype would be too much (I believe).
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.
Ah, no. I do need a vector because of the search function.
But it is not a point, it is actually the x
grid for \vec{y} = f(\vec{x}). I could call them xgrid
, I decided to change it to x
since it is not exposed to the user and only used internally (as opposed to the xgrid
which needs to be filled by the user).
But I can use xgrid
as well here if you prefer (but I'll do the change directly in #5)
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.
"better name" was referring to the name of the type alias I was proposing. x
for the attribute is just fine.
The reason behind the alias is that an XPoint
is isomorphic to a Vec<f64>
, but not all Vec<f64>
are XPoint
s. It doesn't help with automatic check, because an alias is interchangeable with the aliased type, but it helps the developer reading it (to have help also from the compiler, we could use a newtype, but as I said it seems to me that it's not worth, at the moment)
pub fn derivative_at(&self, index: usize) -> f64 { | ||
let dx = self.input[index] - self.input[index - 1]; | ||
let dy = self.values[index] - self.values[index - 1]; | ||
pub fn derivative_at(&'a self, index: usize) -> f64 { |
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.
The annoying part of lifetimes is that you then need to specify them everywhere. Luckily not in calls, but in all types yes...
impl<'a> GridSlice<'a> { | ||
/// Implements utilities for a GridSlice that can be used by cubic interpolation Nd | ||
/// Takes as input the value being queried and its index within the given slice | ||
fn cubic_interpolate_1d(&'a self, query: f64, idx: usize) -> Result<f64, InterpolationError> { |
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.
Does it make sense to call it 1d
? A slice is always 1D... (or are you already planning to extend beyond?)
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.
Who knows… might be necessary in the future.
I'll merge this (so that further discussion can happen in #5 or other future changes) |
I've updated the code so that it is prepared to handle n-d interpolation.
My idea is to have a Vector of 1d-vectors for the input points and a nd-array for the values of the grid. The reason why I've changed array to vector is that, since we know (or enforce during construction if need be) at least for LHAPDF that the input is sorted 1d arrays, using vectors allow to use some of the nice vector methods that are considerably faster than my implementation of the search in ndarray. Indeed, this version is now faster than LHAPDF :)
This is also much easier to generalize to n-dimensions.
Other changes that I've made is to comment out the imports to the
scatter
part of the code, because clippy was making me crazy.