|
| 1 | +#include "utils/norms.hpp" |
1 | 2 | #include <finufft/test_defs.h> |
2 | 3 |
|
3 | 4 | /* Test that execute_adjoint applies the adjoint of execute, in the |
4 | 5 | guru interface, for all types and dimensions. |
5 | 6 | We use the test_defs macros, as with other C-interface tests. |
6 | | - Barnett 7/22/25 - 7/23/25. |
| 7 | + Barnett 7/23/25. More stable relative error denom, discussion 7/29/25. |
7 | 8 |
|
8 | 9 | Test tolerances are hard-wired in this code, not via cmd line. |
9 | 10 | Subtlety is that adjointness is subject to round-off, which is amplified |
10 | | - by the r_dyn dynamic range, per dimension. That can get bad for USF=1.25. |
11 | | - It also appears to vary with platform (macos-13 CI forced us to abandon |
12 | | - allowederr = 1e-13 in favor of 1e-12). |
| 11 | + by the r_dyn dynamic range, per dimension. That can get bad for USF=1.25, |
| 12 | + esp in type 3 (eg by default if eps~1e-8 in double prec). |
13 | 13 | See discussions "Repeatability" and "Adjointness" in docs/trouble.rst |
| 14 | +
|
| 15 | + Discussion of the "correct" denominator to claim "relative" err in (f,F)-(C,c): |
| 16 | + ||Ferr||_2/||F||_2 is our NUFFT metric, as per our SISC 2019 FINUFFT paper. |
| 17 | + Expect err in (f,F) ~ ||f||_2.(error in one entry of F), by rotational |
| 18 | + invariance in C^N (where N is really Nused the number of modes). |
| 19 | + And (err in one entry of F) ~ ||Ferr||_2 / sqrt(N). Combining with the metric |
| 20 | + above, explains our choice denom = ||f||.||F||/sqrt(N) to give a rel measure |
| 21 | + of adjointness error. Has much less fluctuation than old denom=(f,F) which it |
| 22 | + turned out had zero-mean Gaussian pdf (=> fat-tailed pdf of reciprocal, bad!) |
14 | 23 | */ |
15 | 24 |
|
16 | 25 | using namespace std; |
17 | 26 |
|
18 | 27 | int main() { |
19 | 28 |
|
20 | | - BIGINT Ns[3] = {300, 40, 6}; // modes per dim, smallish probs (~1e5 max) |
21 | | - BIGINT M = 100000; // NU pts, smallish, but enough so rand err small |
| 29 | + BIGINT Ns[3] = {200, 40, 6}; // modes per dim, smallish probs (~1e5 max) |
| 30 | + BIGINT M = 50000; // NU pts, smallish, but enough so rand err small |
22 | 31 | int isign = +1; |
23 | 32 | int ntr = 1; // how many transforms (one for now) |
24 | 33 | finufft_opts opts; |
25 | 34 | FINUFFT_DEFAULT_OPTS(&opts); |
26 | 35 | // opts.upsampfac = 1.25; // experts use to override default USF |
27 | 36 | #ifdef SINGLE |
28 | | - FLT tol = 1e-5; // requested transform tol (adj err worse near epsmach) |
29 | | - FLT allowederr = 1e-4; // 1e3*epsmach due to USF=1.25 larger r_dyn |
| 37 | + FLT tol = 1e-6; // requested transform tol (small enough to force USF=2) |
| 38 | + FLT allowederr = 1e-4; // ~1e3*epsmach (allow USF=1.25 larger r_dyn) |
30 | 39 | string name = "adjointnessf"; |
31 | 40 | #else |
32 | | - FLT tol = 1e-12; // requested transform tol (USF=2 guaranteed) |
33 | | - FLT allowederr = 1e-12; // 1e4*epsmach (USF=2 r_dyn^3<1e3, but macos-13 fails) |
| 41 | + FLT tol = 1e-12; // requested transform tol (eps<=1e-9 => USF=2 guaranteed) |
| 42 | + FLT allowederr = 1e-10; // ~1e6*epsmach (USF=2 r_dyn^3<1e3, but allow USF=1.25) |
34 | 43 | string name = "adjointness"; |
35 | 44 | #endif |
36 | 45 |
|
@@ -90,19 +99,24 @@ int main() { |
90 | 99 | FINUFFT_DESTROY(plan); |
91 | 100 |
|
92 | 101 | // measure scalar error (f,F) - (C,c), should vanish by adjointness... |
93 | | - CPX ipc = 0.0, ipf = 0.0; // results for (C,c) and (f,F) |
| 102 | + CPX ipc = 0.0, ipf = 0.0; // inner-prod results for (C,c) and (f,F) |
94 | 103 | for (int i = 0; i < M; i++) ipc += conj(C[i]) * c[i]; |
95 | | - int Nused = (type == 3) ? Nmax : N; // how many modes or freqs used |
| 104 | + int Nused = (type == 3) ? Nmax : N; // how many modes or freqs used |
96 | 105 | for (int j = 0; j < Nused; j++) ipf += conj(f[j]) * F[j]; |
97 | | - FLT err = abs(ipc - ipf) / abs(ipc); // scale rel to innerprod size |
98 | | - cout << " adj rel err " << err << endl; |
| 106 | + |
| 107 | + // denominator for rel error (twonorm in utils.hpp), see discussion at top: |
| 108 | + // FLT denom = twonorm(M,C.data()) * twonorm(M,c.data()) / sqrt(M); // v sim |
| 109 | + FLT denom = twonorm(Nused, F.data()) * twonorm(Nused, f.data()) / sqrt(Nused); |
| 110 | + FLT err = abs(ipc - ipf) / denom; // scale rel to norms of vectors in ipc |
| 111 | + cout << " adj rel err " << err << endl; // "\t denom=" << denom << endl; |
99 | 112 | errmax = max(err, errmax); |
100 | 113 | } |
101 | 114 | } |
102 | 115 |
|
103 | 116 | // report and exit code |
104 | 117 | if (errmax > allowederr || iermax > 0) { |
105 | | - cout << name << " failed! (allowederr=" << allowederr << ")" << endl; |
| 118 | + cout << name << " failed! (allowederr=" << allowederr << ", iermax=" << iermax << ")" |
| 119 | + << endl; |
106 | 120 | return 1; |
107 | 121 | } else { |
108 | 122 | cout << name << " passed" << endl; |
|
0 commit comments