-
-
Notifications
You must be signed in to change notification settings - Fork 357
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
Implement Gaussian Elimination in C++ #258
Implement Gaussian Elimination in C++ #258
Conversation
@@ -259,6 +259,8 @@ In code, this looks like: | |||
[import:1-42, lang:"julia"](code/julia/gaussian_elimination.jl) | |||
{% sample lang="c" %} | |||
[import:13-44, lang:"c_cpp"](code/c/gaussian_elimination.c) | |||
{% sample lang="cpp" %} | |||
[import:6-51, lang:"c_cpp"](code/c++/gaussian_elimination.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.
Link to back_substitution function is missing
d532840
to
2a1d6e4
Compare
@leios no problems, I'll redo implementation this night. Closing the PR. I'll reopen it after redoing the implementation. |
2a1d6e4
to
7d906a5
Compare
Pushed version with gaussian_jordan |
6dbad83
to
5cf1fc3
Compare
// Main loop going through all columns | ||
for (int col = 0; col < cols - 1; ++col) { | ||
// Step 1: finding the maximum element for each column | ||
int max_index = [&]() { |
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 lambda function doesn't help with readability and there is no need too.
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 technique called IIFE stands for an immediately-invoked function expression, it was popularized in JS and adopted in C++. It has some benefits comparing with old style:
- initialize the variable at declaration
- keep local var in the lambda in the scope
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.
Fair enough, but it's still hard to read. I've asked around on discord and people agree.
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.
Sorry, my English is my second language. "People agree" with "fair enough" or "it is hard to read"?
And what is the final decision?
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.
Sorry that was a bad statement I wrote. You have a fair point but I have asked around and people think it's hard to read.
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.
Your suggestion is conflicting with C++ Core Guidelines: ES.5: Keep scopes small, ES.20: Always initialize an object. To avoid this without using lambda I would need to create named function.
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 lambda function makes it hard for people to understand your c++ code if they are new to the language. Also my C code wasn't long:
for (size_t i = k + 1; i < rows; ++i) {
if (fabs(a[i * cols + k]) > fabs(a[pivot * cols + k])) {
pivot = 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.
Let's wait what @leios would say.
- Modern C++ uses lambda quite aggressively it was introduces in C++ 11 more than 7 years ago
- Without IIFE it would be hard to follow C++ Core Guidelines
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.
To be clear, we are comparing the following code samples:
for (int col = 0; col < cols - 1; ++col) {
// Step 1: finding the maximum element for each column
int max_index = [&]() {
int res = row;
int max_element = a[col + row * cols];
for (int r = row + 1; r < rows; ++r) {
if (max_element < std::abs(a[col + r * cols])) {
max_element = std::abs(a[col + r * cols]);
res = r;
}
}
return res;
}();
...
}
and
for (int col = 0; col < cols - 1; ++col) {
// Step 1: finding the maximum element for each column
int max_index = col + row * cols;
for (size_t i = col + 1; i < rows; ++i) {
if (fabs(a[i * cols + col]) > fabs(a[max_index * cols + col])) {
max_index = i;
}
}
...
}
In my opinion, the latter one is easier to read (unless I screwed something up and this doesn't actually do the right action). I feel I am not fully understanding the conversation. @AntonTe How does the second code snippet violate the core guidelines? I don't think it adds another variable to the scope and doesn't violate ES.20.
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.
@leios you right. I'll make changes. The problem in my code is not about using IIFE, but about caching max_element.
int max_index = [&]() { | ||
int res = row; | ||
int max_element = a[col + row * cols]; | ||
for (int r = row + 1; r < rows; ++r) |
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 for loop has no curly bracket.
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.
Will fix it, I did not know about it. Do we have any style guide document, so in the future I would not make this sort of mistakes? Like snake_case and enforced curly brackets?
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 really but I do like snake case and I think we should enforce curly brackets since it's are clearer.
#include <vector> | ||
|
||
void gaussian_elimination(std::vector<double>& a, int cols) { | ||
assert(a.size() % cols == 0); |
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 need for assert
.
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 agree we should not assert on user input, but what you can offer instead?
I can see multiple options:
- remove assert: the code will crash later of even worse return incorrect result
- log the error and crash: same as assert but more lines of code
- return the error code: for production solution it might be OK, but it requires a lot of boiler plate code, and hides the important parts of the code
- throw an exception: for production solution it might be OK, but it requires some of boiler plate code, and hides the important parts of the code
Conclusion: for this particular case, sample code for the book about algorithms I prefer to use assert.
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.
Have an if statement there like if (a.size() % cols != 0) {
.
} | ||
|
||
int main() { | ||
std::vector<double> a = {2, 3, 4, 6, 1, 2, 3, 4, 3, -4, 0, 10}; |
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.
Make this the same as in the julia code.
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.
Ups
for (int i = 0; i < rows; ++i) { | ||
std::cout << "["; | ||
for (int j = 0; j < cols; ++j) { | ||
std::cout << a[j + i * cols] << " "; |
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.
use "\t"
and not a space for better formatting.
if (a[col + row * cols] != 0) { | ||
|
||
// divide row by pivot and leaving pivot as 1 | ||
for (int i = cols - 1; i >= static_cast<int>(col); --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 cast col
from int to int.
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.
ups
std::vector<double> soln(rows); | ||
|
||
// initialize the final element | ||
soln[rows - 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.
you don't need this.
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 copied from Jula code:
# initialize the final element
soln[rows] = A[rows, cols] / A[rows, cols-1]
Is Jula code has a bug?
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'll tell @leios. But looking at the for loop, when i
is rows - 1 sum will be 0. This is because the first for loop will just be jumped making that line useless since it will happen later.
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 should do better job or reading and analyzing the code. Need to run for grocery... will back soon.
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 found the bug, it is next line; should be -2 instead of -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.
It looks better if you remove that line.
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.
You right. I'll make a pull request for Julia code as well.
2125f5c
to
55f0195
Compare
a[i + row * cols] /= a[col + row * cols]; | ||
|
||
// subtract value from above row and set values above pivot to 0 | ||
for (int i = 0; i < static_cast<int>(row - 1); ++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.
I think this should just be row
instead of row - 1
, otherwise gauss-jordan does not put the matrix into rref.
Also: are the static_cast<int>
s necessary here? It's no big deal if they are, I'm just trying to figure out why they are here.
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.
man... so many static_casts... :(
row - 1
looks right, we are not touching current row
we only updating all rows above.
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.
So, when running this with row
, we get the right answer:
[1.000000 0.000000 0.000000 1.636364 ]
[0.000000 1.000000 0.000000 -1.272727 ]
[0.000000 0.000000 1.000000 1.636364 ]
but with row - 1
, we get:
[1.000000 -1.333333 0.000000 3.333333 ]
[0.000000 1.000000 0.705882 -0.117647 ]
[0.000000 0.000000 1.000000 1.636364 ]
which means we are ignoring the row just above the row we want too.
as far as the static_cast
s are concerned, if there's a reason to leave them in, it's no problem. I was more curious about it than anything.
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.
You right, classic off-by-one error.
There are 2 hard problems in computer science: cache invalidation, naming things, and off-by-1 errors.
You can see static_cast in the code, because originally it was explosive mix of unsigned and signed integer, I refactored code to use only signed integers but forgot to remove static_cast.
8a63475
to
60b5d71
Compare
1, 2, 3, 4, | ||
3, -4, 0, 10 }; | ||
const int cols = 4; | ||
if (a.size() % cols != 0) |
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 not have the if statement in the other functions replacing assert
. People are going to take them and not the main function when they need a Gaussian Elimination.
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 I explained different solutions instead of assert, majority of solutions implied using if. So it is not clear for me what you want to put inside if-statement? As I mentioned above:
- log the error and crash
- return the error code
- throw an exception
?
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 you should get rid of all assert
s and not even replace them with if
s. We can just always assume that the input is correct for the sake of simplicity.
If you still want to have some checks, then inside the if
you can have return 0;
or a zero matrix or something.
} | ||
|
||
// Step 2: swap row with highest value for that column to the top | ||
for (int c = 0; c < cols; ++c) |
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.
You should check if row== max_index
, if it's the case you don't need to swap.
- add curly bracket - remove static_cast - remove IIFE - fix format for the matrix - format output with tabs - properly sanitize user input - remove extraneous assignment - fix off-by-1
- remove asserts - don't swap if it is a same row
60b5d71
to
dc87fe0
Compare
a[i + row * cols] /= a[col + row * cols]; | ||
|
||
// subtract value from above row and set values above pivot to 0 | ||
for (int i = 0; i < row; ++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.
Add brackets to this for loop.
Hey guys, what is the status on this PR? |
I think there were only a few smallscale changes requested on this PR, but we have another Gaussian Elimination PR now. If this one is stale, we'll close it and work on the next one. |
This pull request is stale, so I will close it. You had months (since Oct 3) to reply and in that time you've been online on github. |
No description provided.