-
Notifications
You must be signed in to change notification settings - Fork 213
Add predicted_zero_crossing function generalized for Datavectors #4004
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
Add predicted_zero_crossing function generalized for Datavectors #4004
Conversation
| * \brief Predicts the zero crossing of multiple functions contained in a vector | ||
| * of datavectors. |
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.
CamelCase DataVector. Same in the rest of the comments in this commit.
| * Fits a linear function to a set of datavectors for different x_values | ||
| * and uses the fits to predict what x_values, when the y_values are zero, will | ||
| * be crossed for each function contained in the datavector. |
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 comment is a bit cumbersome and misleading. From what it looks like, you aren't fitting one linear function. You're fitting N linear functions where N is the size of y_values. And each of these y_values uses the same x_values for the fit. Then you use each fit to determine the zero crossing corresponding to that y_value. Am I understanding that correctly?
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.
Yes, that's right @knelli2
| for (size_t i = 0; i < y_values.size(); i++) { | ||
| const auto coefficients = predictor.fit_coefficients(x_values, y_values); |
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 will calculate the fit coefficients for all y_values at once. No need to recalculate them every iteration in the loop.
| */ | ||
| DataVector predicted_zero_crossing_value( |
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.
Question: Why did you choose DataVector as the return type? Would std::vector<double> make more sense so that it corresponds to the y_values input? Or does this need to be a DataVector for something else?
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 chose a DataVector for no special reason besides just generalizing the function to return a DataVector type to store the zero crossing values. I think std::vector will achieve the same result. Would a vector be a more efficient return type? @knelli2
| y_values.push_back(DataVector{slope[i] * x_values[0] + b[i] + error, | ||
| slope[i] * x_values[1] + b[i] + error, | ||
| slope[i] * x_values[2] + b[i] + error}); |
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.
Is there a reason why each component has to have the same error? Or can each component have a different error?
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.
No reason besides just adding a small perturbation for each calculation, I think different error values would be better, I can change that if that's preferred. @knelli2
9739743 to
b38222e
Compare
| return -coefficients[0]/coefficients[1]; | ||
| } | ||
|
|
||
| std::vector<double> predicted_zero_crossing_value( |
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.
Not a change request necessarily, but I'm wondering why you return std::vector<double> instead of DataVector?
| * same x_values for the fit. Each fit determines the zero-crossing | ||
| * corresponding to that y_value. | ||
| */ | ||
| std::vector<double> predicted_zero_crossing_value( |
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.
No necessarily a change request, but are we sure we wan this to return std::vector<double> and not DataVector?
| * \brief Predicts the zero crossing of multiple functions contained in a vector | ||
| * of datavectors. | ||
| * | ||
| * Fits a set of N linear functions where N is the size of y_values that use the |
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.
comma after "functions"
Isn't N the size of the DataVector, not the size of y_values? The size of x_values and y_values are the size of the std::vectors<>, which are the number of points used in each fit. N is the number of fits, which is the DataVector size, right?
b38222e to
5532ecf
Compare
|
@geoffrey4444 I think we are going back and forth on the return type of this function. I'm not sure which is best. Since @markscheel will be the one using this in size control, maybe he can let us know which return type is preferred, if any. |
| } | ||
|
|
||
| std::vector<double> predicted_zero_crossing_value( | ||
| const DataVector& x_values, const std::vector<DataVector>& y_values) { |
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 am thinking of the primary use case for this function:
x_values will be a set of times. y_values will be a set of DataVectors, one DataVector for each time, where each DataVector is the characteristic speed evaluated at all points on the surface.
Then the result will be the zero-crossing time for each point on the surface.
So I think x_values should be a std::vector<double>.
Then there could be a check that x_values.size()==y_values.size().
And then I think the return type should be DataVector (with the same size as the DataVector in y_values).
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'm using @gsb76 's LinearLeastSquares function that takes LinearLeastSquares<Order>::fit_coefficients(const T& x_values, const std::vector<T>& y_values) for the arguments to do the fitting for each std::vector<DataVector corresponding to the y_values. Would it be okay to leave as is and just add the check for x_values.size()==y_values.size() and generalize the return type to be just DataVector ? @markscheel
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 think the current implementation of this function will handle the use case I am thinking of.
This is for two reasons: One is that we don't want the user to be copying things into different structures in order to call the function. But the second reason is because I think you can't just call LinearLeastSquares in the same way you do now. Here's why:
The user (in the use cases I am thinking of) will have a std::vector<double> or maybe even a std::deque<double> for the x_values. This will represent a set of times.
The user will also have a std::vector<DataVector> or maybe a std::deque<DataVector> for the y_values, where each DataVector is the characteristic speed at all the points on a surface. The y_values are indexed by yvalues[x_value_index][datavector_index]. This is the opposite indexing order compared to what LinearLeastSquares expects for its y_values.
| return -coefficients[0]/coefficients[1]; | ||
| } | ||
|
|
||
| std::vector<double> predicted_zero_crossing_value( |
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.
Above in the case for doubles, add a check that x_values and y_values have the same size?
| } | ||
|
|
||
| std::vector<double> predicted_zero_crossing_value( | ||
| const DataVector& x_values, const std::vector<DataVector>& y_values) { |
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 think the current implementation of this function will handle the use case I am thinking of.
This is for two reasons: One is that we don't want the user to be copying things into different structures in order to call the function. But the second reason is because I think you can't just call LinearLeastSquares in the same way you do now. Here's why:
The user (in the use cases I am thinking of) will have a std::vector<double> or maybe even a std::deque<double> for the x_values. This will represent a set of times.
The user will also have a std::vector<DataVector> or maybe a std::deque<DataVector> for the y_values, where each DataVector is the characteristic speed at all the points on a surface. The y_values are indexed by yvalues[x_value_index][datavector_index]. This is the opposite indexing order compared to what LinearLeastSquares expects for its y_values.
| slope[i] * x_values[1] + b[i] + error_dist(gen), | ||
| slope[i] * x_values[2] + b[i] + error_dist(gen)}); | ||
| } | ||
|
|
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 3 sizes in this test:
- The size of x_values
- The size of y_values
- The size of the DataVector inside of y_values
All 3 of those sizes are the same. But only 2 of those sizes should be required to be the same, and the other size should be allowed to be different.
Please write a test where only 2 of the sizes are the same.
For the use case we care about, x_values will be a set of times (probably in a std::vector or maybe in a std::deque). Most likely the size of x_values will be something like 10 or 15.
y_values will be a set of DataVectors, where each DataVector is at one of the times in x_values. So there will be something like 10 or 15 DataVectors. But each DataVector will have probably 100 to 400 components (the components correspond to the points on a Strahlkorper, and for a Strahlkorper of Ylm resolution L, the number of points is roughly L^2).
| * \brief Predicts the zero crossing of multiple functions contained in a vector | ||
| * of datavectors. | ||
| * | ||
| * Fits a set of N linear functions, where N is the size of the returned |
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.
In the documentation comment, please add a longer description of what is expected in x_values and y_values. In particular, please specify the indexing order of y_values, please specify which sizes in x_values and y_values are supposed to be the same, and please specify the size of the returned value.
|
One possibility would be something like this (but with more checks added): And most likely all the |
|
More things that need to be done to make a working ZeroCrossingPredictor (not for this PR, but for future PRs).
|
Or maybe infinity? |
Yes infinity would work too; just need something that you can test for in the size control logic. |
5532ecf to
bf7ef4e
Compare
Okay, I think I got the basic version of the function to work now (the corrections for this pr). I can start working on the next implementations with Gabe on the |
markscheel
left a comment
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 correspondence in sizes is the same as in the original version. x_values and y_values need to be the same size. The DataVectors inside y_values should be a different size.
Please update code and the test.
| DataVector tmp_y_values{x_values.size()}; | ||
| for (size_t i = 0; i < result.size(); i++) { | ||
| for (size_t j = 0; j < tmp_y_values.size(); j++) { | ||
| tmp_y_values[j] = y_values[i][j]; |
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.
Shouldn't this be y_values[j][i] ? Each i indicates a point in the DataVector, and each j indicates a point in x_values.
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.
Yes, that's correct. I was not clear on how we needed to index through the y_values in my test which confused me when I was setting the tmp_y_values vector.
| ASSERT(x_values.size() == y_values[0].size(), | ||
| "The x_values and y_values must be of the same size"); |
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 is the wrong ASSERT, and is the same problem as your original version.
x_values.size() should be equal to y_values.size(). y_values[0].size() should have nothing to do with x_values.size().
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.
Yeah, that makes sense now, I should have an ASSERT that checks for the y_values and x_values to be the same size.
| DataVector tmp_x_values{x_values.size()}; | ||
| for (size_t i = 0; i < x_values.size(); i++) { | ||
| tmp_x_values[i] = x_values[i]; | ||
| } |
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.
Why do you need tmp_x_values at all? Does it work if you make tmp_y_values a deque , and then you pass two deques into fit_coefficients? (This is what I thought you were going to do; this is why I thought you added a deque to the instantiations in LinearLeastSquares.cpp)
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, problem fixed, thank you
| "The x_values and y_values must be of the same size"); | ||
| intrp::LinearLeastSquares<1> predictor{x_values.size()}; | ||
|
|
||
| DataVector result(y_values.size()); |
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.
Size of result should be y_values.front().size().
| * The x_values contain the set of values possibly approaching zero while | ||
| * the y_values contain the DataVectors that contain for example the set of | ||
| * points on a Strahlkorper. Each element of y_values is a | ||
| * DataVector of size N. The first index to y_values selects a Strahlkorper | ||
| * and the second index selects a specfic point on that surface. | ||
| * The y_values are indexed by [DataVector][point_on_strahlkorper]. | ||
| * The x_values and y_values must be of the same size and type. |
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 x_values contain the set of values possibly approaching zero while | |
| * the y_values contain the DataVectors that contain for example the set of | |
| * points on a Strahlkorper. Each element of y_values is a | |
| * DataVector of size N. The first index to y_values selects a Strahlkorper | |
| * and the second index selects a specfic point on that surface. | |
| * The y_values are indexed by [DataVector][point_on_strahlkorper]. | |
| * The x_values and y_values must be of the same size and type. | |
| * Each linear function is fit by M points, where M is the size of x_values and y_values. | |
| * The Nth point in the returned DataVector is the value of x for which the linear fit to | |
| * the points (x[:], y[:][N]) [using python notation] crosses y=0. | |
| * The x_values contain M points in the independent variable, while | |
| * the y_values contain M DataVectors that each contain (for example) the set of | |
| * points on a Strahlkorper. Each element of y_values is a | |
| * DataVector of size N. The first index to y_values selects a point corresponding | |
| * to an x_value, and the second index selects a point in the DataVector, | |
| * so the y_values are indexed by [x_value_index][point_in_datavector]. | |
| * The x_values and y_values must be of size M. |
bae1456 to
19eac89
Compare
|
Okay, I made all the necessary changes @markscheel |
markscheel
left a comment
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.
Looks good. One small naming change; please squash immediately.
| } | ||
| CAPTURE(slope); | ||
|
|
||
| std::deque<double> b{}; |
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.
Is there a better name than b?
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.
y_intercept?
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.
y_intercept works
b0e7b96 to
99e2be0
Compare
|
Everything looks good to me. @MarloMo could you rebase this on the latest develop one more time? There were some recent CI changes that just got merged so not all the required checks have been run. |
99e2be0 to
74a41f0
Compare
|
Looks like there was a |
|
Actually I think it was the GH constraints test, but either way it's unrelated to this PR so I wouldn't worry about it. Side note: @sxs-collaboration/spectre-core-devs, didn't we just increase the timeout of the GH constraints test? |
74a41f0 to
b1017d3
Compare
Proposed changes
The Predicted Zero Crossing function fits a linear function to a set of y_values passed through as a set of DataVector 's at different times and uses the fit to predict what times the value zero will be crossed. This is a step toward measuring how long the surface will take to hit the excision surface so that the control system can adjust the grid to avoid the excision surface from coming out of the apparent horizon surface. The function now takes a
DataVectorfor the x_values and astd::vector<DataVector>for the y_values.Upgrade instructions
Code review checklist
make docto generate the documentation locally intoBUILD_DIR/docs/html.Then open
index.html.code review guide.
bugfixornew featureif appropriate.Further comments