Skip to content
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

Revert "Merge pull request #290" #570

Merged
merged 4 commits into from
Aug 30, 2021

Conversation

weslleyspereira
Copy link
Collaborator

@weslleyspereira weslleyspereira commented May 26, 2021

This reverts commit 8d23489, reversing changes made to c3b03d8.

Description

The discussion in #569 put some doubts on the range of valid inputs for (s,d)COMBSSQ. See #569 for some examples where (s,d)COMBSSQ fails. These routines were introduced in #290 to improve the accuracy of the xLANxx subroutines.

This PR removes (s,d)COMBSSQ from the library, which brings us back to the old strategy: use xLASSQ to combine the scaled sums.

Edited: This PR closes #424

Checklist

This reverts commit 8d23489, reversing
changes made to c3b03d8.
@codecov
Copy link

codecov bot commented May 26, 2021

Codecov Report

Merging #570 (aaeeef2) into master (bd6add2) will decrease coverage by 82.37%.
The diff coverage is 0.00%.

Impacted file tree graph

@@             Coverage Diff             @@
##           master     #570       +/-   ##
===========================================
- Coverage   82.37%    0.00%   -82.38%     
===========================================
  Files        1894     1892        -2     
  Lines      190679   198735     +8056     
===========================================
- Hits       157065        0   -157065     
- Misses      33614   198735   +165121     
Impacted Files Coverage Δ
SRC/clangb.f 0.00% <0.00%> (-70.74%) ⬇️
SRC/clange.f 0.00% <0.00%> (-100.00%) ⬇️
SRC/clanhb.f 0.00% <0.00%> (-57.90%) ⬇️
SRC/clanhe.f 0.00% <0.00%> (-100.00%) ⬇️
SRC/clanhp.f 0.00% <0.00%> (-60.72%) ⬇️
SRC/clanhs.f 0.00% <0.00%> (-84.22%) ⬇️
SRC/clansb.f 0.00% <0.00%> (-16.67%) ⬇️
SRC/clansp.f 0.00% <0.00%> (-38.38%) ⬇️
SRC/clansy.f 0.00% <0.00%> (-65.52%) ⬇️
SRC/clantb.f 0.00% <0.00%> (-55.84%) ⬇️
... and 1892 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bd6add2...aaeeef2. Read the comment docs.

@weslleyspereira
Copy link
Collaborator Author

Running tests with:

My system:

  • Intel(R) Core(TM) i7-10710U CPU @ 1.10GHz
  • 7.8 GB RAM
  • Ubuntu 18.04.5 LTS (5.4.0-70-generic)
  • GNU compilers, GCC version 7.5.0
  • CFLAGS = -O3 and FFLAGS = -O2 -frecursive

Results:

  1. master at c3b03d8 (before Update Frobenius norms for better accuracy. #290):
Wed May 26 15:16:25 2021
./tester  --verbose 1 --type s,d,c,z --align 32 --dim 4000 --norm fro lange
LAPACK++ version 2020.10.02, id 0eb8400
input: ./tester --verbose 1 --type 's,d,c,z' --align 32 --dim 4000 --norm fro lange
                                                   LAPACK++        Ref.          
type     norm       m       n  align      error    time (s)    time (s)  status  

A m= 4000, n= 4000, lda= 4000
norm_tst = 2.28695190e+03
norm_ref = 2.28695190e+03
test matrix A: rand, cond(S) = NA
   s      fro    4000    4000     32   0.00e+00     0.04597     0.05621  pass    


A m= 4000, n= 4000, lda= 4000
norm_tst = 2.30912303e+03
norm_ref = 2.30912303e+03
   d      fro    4000    4000     32   0.00e+00     0.04700     0.05795  pass    


A m= 4000, n= 4000, lda= 4000
norm_tst = 3.18711377e+03
norm_ref = 3.18711377e+03
   c      fro    4000    4000     32   0.00e+00     0.09132      0.1071  pass    


A m= 4000, n= 4000, lda= 4000
norm_tst = 3.26590201e+03
norm_ref = 3.26590201e+03
   z      fro    4000    4000     32   0.00e+00     0.09260      0.1143  pass    
All tests passed for lange.
pass

All routines passed.
Elapsed 2.58 sec
Wed May 26 15:16:27 2021

Using double precision (type d) as reference, this gives a relative error for single precision (type s) of 9.6015e-03.

  1. Current branch at 4ea5c1e:
Wed May 26 16:34:24 2021
./tester  --verbose 1 --type s,d,c,z --align 32 --dim 4000 --norm fro lange
LAPACK++ version 2020.10.02, id 0eb8400
input: ./tester --verbose 1 --type 's,d,c,z' --align 32 --dim 4000 --norm fro lange
                                                   LAPACK++        Ref.          
type     norm       m       n  align      error    time (s)    time (s)  status  

A m= 4000, n= 4000, lda= 4000
norm_tst = 2.30936792e+03
norm_ref = 2.30936792e+03
test matrix A: rand, cond(S) = NA
   s      fro    4000    4000     32   0.00e+00     0.02936     0.03555  pass    


A m= 4000, n= 4000, lda= 4000
norm_tst = 2.30912303e+03
norm_ref = 2.30912303e+03
   d      fro    4000    4000     32   0.00e+00     0.02205     0.03970  pass    


A m= 4000, n= 4000, lda= 4000
norm_tst = 3.26619067e+03
norm_ref = 3.26619067e+03
   c      fro    4000    4000     32   0.00e+00     0.04240     0.05933  pass    


A m= 4000, n= 4000, lda= 4000
norm_tst = 3.26590201e+03
norm_ref = 3.26590201e+03
   z      fro    4000    4000     32   0.00e+00     0.04327     0.06538  pass    
All tests passed for lange.
pass

All routines passed.
Elapsed 2.34 sec
Wed May 26 16:34:26 2021

Using double precision (type d) as reference, this gives a relative error for single precision (type s) of 1.0605e-04.

  1. master at bd6add2 (before applying the changes from this branch):
Wed May 26 16:44:58 2021
./tester  --verbose 1 --type s,d,c,z --align 32 --dim 4000 --norm fro lange
LAPACK++ version 2020.10.02, id 0eb8400
input: ./tester --verbose 1 --type 's,d,c,z' --align 32 --dim 4000 --norm fro lange
                                                   LAPACK++        Ref.          
type     norm       m       n  align      error    time (s)    time (s)  status  

A m= 4000, n= 4000, lda= 4000
norm_tst = 2.30937085e+03
norm_ref = 2.30937085e+03
test matrix A: rand, cond(S) = NA
   s      fro    4000    4000     32   0.00e+00     0.02963     0.03561  pass    


A m= 4000, n= 4000, lda= 4000
norm_tst = 2.30912303e+03
norm_ref = 2.30912303e+03
   d      fro    4000    4000     32   0.00e+00     0.02254     0.03499  pass    


A m= 4000, n= 4000, lda= 4000
norm_tst = 3.26619287e+03
norm_ref = 3.26619287e+03
   c      fro    4000    4000     32   0.00e+00     0.04273     0.06186  pass    


A m= 4000, n= 4000, lda= 4000
norm_tst = 3.26590201e+03
norm_ref = 3.26590201e+03
   z      fro    4000    4000     32   0.00e+00     0.04479     0.06828  pass    
All tests passed for lange.
pass

All routines passed.
Elapsed 2.40 sec
Wed May 26 16:45:01 2021

Using double precision (type d) as reference, this gives a relative error for single precision (type s) of 1.0732e-04.

Summary:

  • Using the test from LAPACK++ and (s,d)LANGE in c3b03d8, I was able to found a relative difference of 9.6015e-03 between single and double precision norms. This value is not too far from 1.5360e-02 found in Update Frobenius norms for better accuracy. #290.
  • In the current state of the LAPACK repository, tests with (4ea5c1e) and without (bd6add2) xCOMBSSQ have similar accuracy.

@weslleyspereira
Copy link
Collaborator Author

weslleyspereira commented Jun 16, 2021

Hi. I did more tests on my laptop.

LAPACK compiled with gfortran FFLAGS = -g -frecursive

Source: dlange-test-exact-float.cpp: https://gist.github.com/weslleyspereira/75d01789f019b213f9a9c0c14cbbfe76

// Copyright 2021 Weslley S Pereira

#include <cmath>
#include <limits>
#include <iostream>
#include <bitset>

using Integer = std::int32_t;

extern "C" {
    double dlange_(
        char* norm, Integer* m, Integer* n,
        const double* A, Integer* lda,
        double* work, std::size_t norm_len);
}

template<typename T>
void print_binary( const T& a ){
    const char* a_char = reinterpret_cast<const char*>(&a);
    for (size_t i = 0; i < sizeof(T); ++i)
    {
        std::bitset<8> x(a_char[sizeof(T)-i-1]);
        std::cout << x;
    }
    std::cout << std::endl;
}

int main() {
    using Real = double;

    auto normid = 'F';
    const Integer N = 3793;
    Integer m, n;
    Integer *lda = &m;
    auto work = std::numeric_limits<Real>::quiet_NaN();
    Real norm, expectedNorm, a, *A;

    a = pow(2, 1-1023);
    std::cout << "a            = " << a << " = (memory) ";
    print_binary( a );
    
    m = N;
    n = N;
    A = new Real[m*n];
    for (Integer i = 0; i < m*n; ++i) A[i] = a;

    expectedNorm = N*a;
    std::cout << "expectedNorm = " << expectedNorm << " = (memory) ";
    print_binary( expectedNorm );

    norm = dlange_(&normid, &m, &n, A, lda, &work, 1);
    std::cout << "dlange       = " << norm << " = (memory) ";
    print_binary( norm );

    if(!std::isfinite(norm) || norm != expectedNorm) {
        std::fprintf(
            stderr, "\nexpected norm %8.2e, got %e\n", expectedNorm, norm);
    }

    delete[] A;

    return 0;
}

Results:

  1. master at c3b03d8 (before Update Frobenius norms for better accuracy. #290):
weslley@lmpnote03:~/repositories/ballistic/565 - issue$ g++ -Wextra -Wall -pedantic -std=c++11 dlange-test-exact-float.cpp ~/repositories/lapack/build_c3b03d8/lib/liblapack.a -g -lgfortran -o dlange-test-exact-float-c3b03d8
weslley@lmpnote03:~/repositories/ballistic/565 - issue$ ./dlange-test-exact-float-c3b03d8
a            = 2.22507e-308 = (memory) 0000000000010000000000000000000000000000000000000000000000000000
expectedNorm = 8.43971e-305 = (memory) 0000000011001101101000100000000000000000000000000000000000000000
dlange       = 8.43971e-305 = (memory) 0000000011001101101000100000000000000000000000000000000000000000
  1. Current branch at 4ea5c1e:
weslley@lmpnote03:~/repositories/ballistic/565 - issue$ g++ -Wextra -Wall -pedantic -std=c++11 dlange-test-exact-float.cpp ~/repositories/lapack/build_4ea5c1e/lib/liblapack.a -g -lgfortran -o dlange-test-exact-float-4ea5c1e
weslley@lmpnote03:~/repositories/ballistic/565 - issue$ ./dlange-test-exact-float-4ea5c1e
a            = 2.22507e-308 = (memory) 0000000000010000000000000000000000000000000000000000000000000000
expectedNorm = 8.43971e-305 = (memory) 0000000011001101101000100000000000000000000000000000000000000000
dlange       = 8.43971e-305 = (memory) 0000000011001101101000100000000000000000000000000000000000001100

expected norm 8.44e-305, got 8.439705e-305
  1. master at bd6add2 (before applying the changes from this branch):
weslley@lmpnote03:~/repositories/ballistic/565 - issue$ g++ -Wextra -Wall -pedantic -std=c++11 dlange-test-exact-float.cpp ~/repositories/lapack/build_bd6add2/lib/liblapack.a -g -lgfortran -o dlange-test-exact-float-bd6add2
weslley@lmpnote03:~/repositories/ballistic/565 - issue$ ./dlange-test-exact-float-bd6add2
a            = 2.22507e-308 = (memory) 0000000000010000000000000000000000000000000000000000000000000000
expectedNorm = 8.43971e-305 = (memory) 0000000011001101101000100000000000000000000000000000000000000000
dlange       = 8.43971e-305 = (memory) 0000000011001101101000100000000000000000000000000000000000000000

Conclusion:

Using both the recent master branch bd6add2 and master at c3b03d8 (before #290), we obtain the exact norm. The modifications from this PR (which reverts 8d23489) introduce errors in the computation. I will investigate what is happening.

@weslleyspereira weslleyspereira marked this pull request as draft June 16, 2021 18:04
!> If scale * sqrt( sumsq ) > tbig then
!>    we now require:   scale >= sqrt( TINY*EPS ) / sbig   on entry,
!> and if scale * sqrt( sumsq ) < tsml then
!>    we now require:   scale <= sqrt( HUGE ) / ssml       on entry,
!> where
!>    tbig -- upper threshold for values whose square is representable;
!>    sbig -- scaling constant for big numbers; \see la_constants.f90
!>    tsml -- lower threshold for values whose square is representable;
!>    ssml -- scaling constant for small numbers; \see la_constants.f90
!> and
!>    TINY*EPS -- tiniest representable number;
!>    HUGE     -- biggest representable number.
@weslleyspereira
Copy link
Collaborator Author

I found one operation with precision loss in the xSLASSQ subroutines. My last commit (bb6d5cf) replaces

!
!  Put the existing sum of squares into one of the accumulators
!
   if( sumsq > zero ) then
      ax = scl*sqrt( sumsq )
      if (ax > tbig) then
         abig = abig + (ax*sbig)**2
         notbig = .false.
      else if (ax < tsml) then
         if (notbig) asml = asml + (ax*ssml)**2
      else
         amed = amed + ax**2
      end if
   end if

by

!
!  Put the existing sum of squares into one of the accumulators
!
   if( sumsq > zero ) then
      ax = scl*sqrt( sumsq )
      if (ax > tbig) then
!        We assume scl >= sqrt( TINY*EPS ) / sbig
         abig = abig + (scl*sbig)**2 * sumsq
         notbig = .false.
      else if (ax < tsml) then
!        We assume scl <= sqrt( HUGE ) / ssml
         if (notbig) asml = asml + (scl*ssml)**2 * sumsq
      else
         amed = amed + scl**2 * sumsq
      end if
   end if

So, I keep the original algorithm but don't compute sqrt( sumsq ) ** 2. However, this new version requires:

!> If scale * sqrt( sumsq ) > tbig then
!>    we require:   scale >= sqrt( TINY*EPS ) / sbig   on entry,
!> and if scale * sqrt( sumsq ) < tsml then
!>    we require:   scale <= sqrt( HUGE ) / ssml       on entry,
!> where
!>    tbig -- upper threshold for values whose square is representable;
!>    sbig -- scaling constant for big numbers; \see la_constants.f90
!>    tsml -- lower threshold for values whose square is representable;
!>    ssml -- scaling constant for small numbers; \see la_constants.f90
!> and
!>    TINY*EPS -- tiniest representable number;
!>    HUGE     -- biggest representable number.

This is reasonable. It basically says that:

  1. If the scaled sum on entry is big then its scaling factor should not be too small.
  2. If the scaled sum on entry is small then its scaling factor should not be too big.

In floating-point arithmetic with base base, t mantissa digits, and exponent exp_min <= e < exp_max:

  • A sufficient condition for (1) is scale >= base ** ( ( exp_min + exp_max ) / 2 ).
  • A sufficient condition for (2) is scale <= base ** ( ( exp_min + exp_max ) / 2 - ( t+1/2 ) ).

The output pair ( scale, sumsq ) from xLASSQ satisfies those conditions.


Current branch at e7d572c:

weslley@lmpnote03:~/repositories/ballistic/565 - issue$ g++ -Wextra -Wall -pedantic -std=c++11 dlange-test-exact-float.cpp ~/repositories/lapack/build_e7d572c/lib/liblapack.a -g -lgfortran -o dlange-test-exact-float-e7d572c
weslley@lmpnote03:~/repositories/ballistic/565 - issue$ ./dlange-test-exact-float-e7d572c
a            = 2.22507e-308 = (memory) 0000000000010000000000000000000000000000000000000000000000000000
expectedNorm = 8.43971e-305 = (memory) 0000000011001101101000100000000000000000000000000000000000000000
dlange       = 8.43971e-305 = (memory) 0000000011001101101000100000000000000000000000000000000000000000

@weslleyspereira weslleyspereira marked this pull request as ready for review June 18, 2021 23:13
@weslleyspereira
Copy link
Collaborator Author

I can also propose an algorithm that avoids the requirements over scale from #570 (comment)
The alternative would be to add extra checks over the variable scl.

I prefer to just ask for scl to be well behaved on entry, as I did in commit e7d572c

@weslleyspereira
Copy link
Collaborator Author

This PR closes #424

langou
langou previously approved these changes Jun 23, 2021
Copy link
Contributor

@langou langou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nicely done @weslleyspereira.
(1) I like the fact that we remove xCOMBSSQ, it is better to have a good xLASSQ than to have a bad xLASSQ fixed by xCOMBSSQ on top, now that Ed Anderson improved xLASSQ, it is possible to remove xCOMBSSQ, perfect.
(2) good job for fixing a small inaccuracy problem in xLASSQ, a few more multiplications, but this is worse it for the improved accuracy for this range of numbers.
Sounds great to me.

SRC/dlassq.f90 Outdated
@@ -189,12 +200,14 @@ subroutine DLASSQ( n, x, incx, scl, sumsq )
if( sumsq > zero ) then
ax = scl*sqrt( sumsq )
if (ax > tbig) then
abig = abig + (ax*sbig)**2
! We assume scl >= sqrt( TINY*EPS ) / sbig
abig = abig + (scl*sbig)**2 * sumsq
notbig = .false.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue but I think that setting notbig here is unnecessary, it's not read again.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. Thanks for noticing that!

@@ -189,12 +200,14 @@ subroutine DLASSQ( n, x, incx, scl, sumsq )
if( sumsq > zero ) then
ax = scl*sqrt( sumsq )
if (ax > tbig) then
abig = abig + (ax*sbig)**2
! We assume scl >= sqrt( TINY*EPS ) / sbig
abig = abig + (scl*sbig)**2 * sumsq
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if scl=1 and sumsq=inf (assuming inf is a valid input for sumsq)? In the previous version, ax would be inf and so would be abig. In the new version, sbig**2 underflows to 0, and times inf gives abig = NaN. I think this is unexpected and undesired.

I've made these changes in our Go port and this is just one of many test cases that started to fail and that is easy to reason about. I haven't looked into the rest, I wanted to share this quickly before the PR is merged.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if scl=1 and sumsq=inf (assuming inf is a valid input for sumsq)? In the previous version, ax would be inf and so would be abig. In the new version, sbig**2 underflows to 0, and times inf gives abig = NaN. I think this is unexpected and undesired.

scl=1 and sumsq=inf would be an invalid input for the proposed change since, in this case, 1 < sqrt( TINY*EPS ) / sbig. If we want to deal if this case, we should add another if-tense inside the scope of if (ax > tbig) then.

Do you think this is an important use case for LASSQ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think this is an important use case for LASSQ?

Not at all. I've re-read your comments above about why this change is what it is and now it all clicked. IIUC, the tradeoff is a slightly limited input range for better accuracy. I will adjust our tests accordingly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for fun. If we want to support all input ranges, the code would be something like:

!
!  Put the existing sum of squares into one of the accumulators
!
   if( sumsq > zero ) then
      ax = scl*sqrt( sumsq )
      if (ax > tbig) then
         if ( scl >= sqrt( TINY*EPS ) / sbig ) then
            abig = abig + (scl*sbig)**2 * sumsq
         else if ( scl < tsml ) then
            abig = abig + (scl*ssml)**2 * (sbig/ssml)**2 * sumsq
         else
            abig = abig + (scl**2 * sbig) * (sbig * sumsq)
         end if
      else if (ax < tsml) then
         if (notbig) then
           if ( scl <= sqrt( HUGE ) / ssml ) then
             ...

Maybe a little smaller if sqrt( TINY*EPS ) / sbig <= sqrt( HUGE ) / ssml. I didn't check. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The upside of this would be that it would avoid the need to document all the constants and thresholds in the documentation for DLASSQ but I'm not in a position to decide whether this should be done or not. Feel free to re-approve and merge.

!> If scale * sqrt( sumsq ) > tbig then
!> we require: scale >= sqrt( TINY*EPS ) / sbig on entry,
!> and if scale * sqrt( sumsq ) < tsml then
!> we require: scale <= sqrt( HUGE ) / ssml on entry,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's nitpicking but documented like this isn't scale=1, sumsq=0 excluded?

@langou langou merged commit 960b60e into Reference-LAPACK:master Aug 30, 2021
vladimir-ch added a commit to gonum/gonum that referenced this pull request Sep 1, 2021
Remove Dcombssq and rely on Dlassq to combine scaled sums. See the
upstream change in Reference-LAPACK/lapack#570
vladimir-ch added a commit to gonum/gonum that referenced this pull request Sep 2, 2021
Remove Dcombssq and rely on Dlassq to combine scaled sums. See the
upstream change in Reference-LAPACK/lapack#570
@julielangou julielangou added this to the LAPACK 3.10.0 milestone Nov 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Sometimes NAN is not propagated from DCOMBSSQ
4 participants