double mynan = 0.0/0.0;
double ret = std::max(0.0, mynan);
is equivalent to
double ret = 0.0.
Now take a FieldMatrix A filled with NaNs and call A.infinity_norm(). That method will loop over A's rows, and for each row r, compare the current maximum (initialised as 0) to r.one_norm(). Since r.one_norm() is NaN for all rows, it will first take std::max(0.0, NaN), which -- as stated above -- is 0.0, then proceed in this manner and eventually return 0.0.
So the inf-Norm of a matrix, which consists solely of NaNs is zero. Is that expected and desirable?
Designs
Child items
...
Show closed items
Linked items
0
Link issues together to show that they're related.
Learn more.
Thanks. I wouldn't want to rule out the case that somebody calls this for a 0x0 matrix. In that case the old code returned 0, and your new code will crash. Can you adapt your patch such that it returns 0 on a 0x0 matrix?
Patch has been applied in revision 6819. Many thanks!
Not surprisingly, the same bug exists in densevector.
Could you be so kind and provide a patch for that too?
First things first: I introduced a subtle bug in FieldMatrix::infinity_norm_real() that's already been pushed. Sorry about that. Also, the list of patches above is getting a bit confusing (darn you, flyspray!).
So I'll attach three patches to this comment:
0001-Fix-FieldVector-infinity_norm-in-analogy-to-r6819.patch: Addresses the Nan-problem for infinity_norm() and infinity_norm_real() of the FieldVector class
0002-Add-a-test-for-FS-1147.patch: Adds tests for the NaN-handling of FieldVector and FieldMatrix
0003-Fix-up-r6819-Add-a-test-case.patch: Fixes the bug I mentioned in FieldMatrix::infinity_norm_real(). Adds corresponding tests, too.
@christi: Did that and rebased the other patches on top of that. That leaves five patches (please ignore all the others):
0001-Do-not-compare-iterators-Let-s-hope-the-optimiser-ca.patch: Amendment to the patch that's already been pushed in response to your (Christian's) critique
0002-Fix-FieldVector-infinity_norm-in-analogy-to-r6819.patch: Addresses the NaN-Problem for fieldvectors
0003-Add-a-test-for-FS-1147.patch: Adds a test for both vectors and matrices. Catches the NaN problem and the following bug.
0004-Fix-up-r6819-Add-a-test-case.patch: Fixes a bug I introduced in FMatrix::infinity_norm_real() through the NaN-Fix that's already been pushed
0005-Make-infinity_norm-generic.patch: An attempt to factor out code and keep bugs like the one mentioned above from happening. Not exactly pretty, though.
I just committed the first two, plus the fix-up part of 0004. Thanks a lot! I would push 0005, but indeed it doesn't compile with gcc-4.7. Any ideas?
It is nice to have the tests from 0003-0004. However, traditionally we have grouped all tests for a single class into a single file. Hence can I ask you to merge you new tests into the files that test FieldMatrix and FieldVector.
where the last line would previously yield 0 and now NaN (which I think is an improvement).
The way I came across this issue was that I computed an error, my code told me the error had infinity norm 0 yet I could clearly see that my code was misbehaving. It turned out that the error computation had failed completely and the error vector consisted of NaNs only. From my point of view, a norm of 0 did not make any sense.
For someone who uses NaN to signify incomplete (or in this case entirely missing) data, I take it the 0 norm actually would.
Now here's the issue: While the infinity norm computation for vectors with no or exclusively NaN entries is now in some sense sane, anything inbetween is highly surprising to me.
My expectation was that NaN entries would simply be ignored (again, undesirable given the current all-NaN-behaviour), since I assumed the following to hold true: std::max(x,NaN) and std::max(NaN, x) both return x. Yet somehow e.g. std::max<double> behaves quite differently from std::fmax, creating the following bizarre situation:
Vv={mynan,mynan,mynan};v.infinity_norm();// nanVv={1,mynan,mynan};v.infinity_norm();// 1(!)Vv={mynan,1,mynan};v.infinity_norm();// nanVv={mynan,mynan,1};v.infinity_norm();// nan
I've reduced this a bit to the following behaviour:
Since std::max_element uses std::max internally, it, too, returns different values for the std::vectors { 2, mynan } and { mynan, 2 } by the way.
I'm not sure if there's a sane way to handle all this without explicit calls to isnan for every entry, which would be far too costly... Maybe we should actually leave NaN handling entirely to the user (again)?
(Deleted my previous comment because it was entirely wrong)
So the upshot is then: std::max makes assumptions of comparability that special values of double violate, hence std::max can only be used with finite double values.
If the user knows her field type to be double, then using std::fmax(x,y) instead of std::max(x,y) has the advantage of being invariant under x-y-swapping. In contrast to what I believe we want here, std::fmax treats NaN values as missing data, so that it, too, cannot be used here, though...
So maybe the only workable solution is to assume the user will sanitise her data, so that vectors will never contain NaN values and continue to use std::max (plus revert my changes, which would make the code a lot nicer again).
This bug has gotten rather messy over time and I feel that I should state more clearly what it's really about and why you might want to care.
For a vector, there are currently two types of norms: norms that sum entries somehow, like the one_norm() and the two_norm(), and a norm that doesn't, namely infinity_norm. Because any sum that contains NaN somewhere will be NaN, the summing norms will always return NaN as soon as a single vector entry is NaN. This is not so for the infinity_norm(), which I find inconsistent.
I made the situation infinitesimally better by supplying a fix to infinity_norm() for the case where the vector consists solely of NaN entries (thinking back then that this was the only situation that caused problems). At the same time, I made things a bit worse because now the order of the vector entries actually matters. The vector { NaN, 2.0 } will have norm NaN whereas { 2.0, NaN } will have norm 2.0. This is because instead of std::fmax, the current implementation uses std::max. Now one might suggest a switch to std::fmax for floating point types, which would remove the order dependence, but it would still be so that any vector that contains even a single proper number would not have infinity_norm() return NaN.
One could of course role own's own mymax() that applies isnan() to both of its arguments. I don't know how costly such calls would be, but they would actually be a bit wasteful here because twice as many NaN checks as necessary would be carried out in the infinity_norm() loop this way. One could instead check for NaN in the loop itself but would have to specialise this function for floating point types then, and finally one would have to answer if all of this is really worth it or if the user should really make sure her vectors are free of NaNs (this kind of added security check could also be enabled through a define).
I did some experiments and measured some timings. If you do it correctly, you will have no performance penalty compared to the "standard" implementation (below named version 0).
After compiling with g++-5.2 -O3 I get the following timings, these do not change when using sse4.2. They might differ somewhat for other architectures:
> ./test_inf_normVerify Implementations Version 0 failed Version 1 works Version 2 works Version 3 works Version 4 works Version 5 works Version 6 works----v = double[1000000000]v[999999999] = NaN Version 0 1.65497 ms Version 1 3.54432 ms Version 2 2.23769 ms Version 3 2.30446 ms Version 4 0.965323 ms Version 5 1.28024 ms Version 6 1.27563 ms----
The funny thing is that with clang-3.7 the timings are significantly different. Apperently gcc is much better at optimizing the code:
v = double[1000000000]v[999999999] = NaN Version 0 5.18355 ms Version 1 3.81165 ms Version 2 4.44596 ms Version 3 4.46749 ms Version 4 4.52507 ms Version 5 4.76469 ms Version 6 4.75915 ms
With g++-4.9 I get roughly the same timings as with g++-5.2. Looking at the numbers, version 1 is never really fast. On the other hand, g++ manages to get improved performance for a couple of implementations. Version 4 is the fastest for g++, while version 5 and 6 will have a better performance, if the NAN is not the last value.
Oh! And the numbers do not change if I have no NAN entry. I even tested a version of the code, where every value in v is initialized with a random value, but again this gave to the same timings, just that 90% of the time was consumed by the std::rand.
In summary I suggest to implement version 4. I tracks the NAN by computing the sum of all values and at the end corrects the max with the NAN.
I updated the test and added a 7th version, which is basically the the same version as 4, but instead of computing a normalization, it explicitly tests for NAN. I also updated version 4 to start the sum with 1 ans sums abs(v[i]), so that it doesn't matter if the whole vector is 0.
I further added a second test, which splits the vector into tiny chunks of size 10.
In the first timing test version 7 and version 4 perform comparably, but in the second test (which is much harder) version 4 takes ~1.5 times longer than version 0 and version 7 take twice as long. All other versions are considerably more expensive.
The funny thing is that with clang-3.7 the timings are significantly different. Apperently gcc is much better at optimizing the code:
This might be related to https://llvm.org/bugs/show_bug.cgi?id=25566 which suggests that one should benchmark with clang 3.5 rather than with 3.7 (I cannot comment on 3.6).
@pipping Interesting ... and yes, with clang-3.6 everything has roughly the same timings as with g++. What still puzzles me is that in the 10^9 entries test run, I get better timing for version 4 than for the (wrong) standard implementation.
The other funny thing is that for clang version 7 is slightly faster than version 4, while for g++ version 4 was always faster, sometime even a factor 1.5.
I think that's related to caching. If I reverse the order, in timing_tests and use
for (int version = 7; version >= 0; version--) {
with with g++ 4.9, I get something like
Version 7 nan 1.47424 ms Version 6 nan 1.08488 ms Version 5 nan 1.08359 ms Version 4 nan 0.813823 ms Version 3 nan 1.00453 ms Version 2 nan 1.00366 ms Version 1 nan 2.98307 ms Version 0 nan 0.815933 ms
so that version 4 is longer faster than version 0.
Finally, it might be worth mentioning that the differences between version 4 and version 7 essentially disappear for me (both with gcc and clang) if I compile with -O2 -march=native.
you are absolutely right, the caching is an issue here, I'd assume running version 0 once to avoid cold caches and then doing the benchmark should be sufficient.
Today, when explaining the issue to Jö, I had an other idea. I'll update the test try it out.
I just performed a test for boost::rational, which does not have a NaN value. Version 4 obviously goes through, but the performance loss is really big. Perhaps we will have to specialize for field types with NaN and for types without. If we do so, we could instead of version 4 also use version 7.
Update: Never mind, I misread entirely what you wrote. What should we specialise to then? Certainly anything for which std::is_floating_point<T>::value is true. But also mpfr_float from boost/multiprecision/mpfr.hpp?
I've now removed version 7 for said reason and extended the snippet to cover std::complex<double> (Please see https://gitlab.dune-project.org/snippets/13). Here, it really doesn't seem to matter all that much which version one goes with.
Combined with the double performance, this suggests that we might want to specialise infinity_norm() and use version 4 for floating point types.