mirror of
https://github.com/paboyle/Grid.git
synced 2026-10-23 07:33:19 +01:00
Compare commits
932
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c1ea017f3d | ||
|
|
e6cae41480 | ||
|
|
5127cfb189 | ||
|
|
e930240878 | ||
|
|
3232b438f2 | ||
|
|
f7c25052f4 | ||
|
|
38c6fa78b2 | ||
|
|
259936a3ed | ||
|
|
7a9cdb45bc | ||
|
|
44f24f395e | ||
|
|
964f4c1271 | ||
|
|
af9d829336 | ||
|
|
b07ccf3d5e | ||
|
|
260d7d2600 | ||
|
|
809249caa9 | ||
|
|
94f529bd63 | ||
|
|
e6d4daf194 | ||
|
|
402fa0aace | ||
|
|
d31061cd49 | ||
|
|
3aa03d3b3f | ||
|
|
87baaf1d37 | ||
|
|
45c1a525b9 | ||
|
|
3a3a20b8c9 | ||
|
|
3bd5e8883f | ||
|
|
865c338dd2 | ||
|
|
e583079ab9 | ||
|
|
507144c457 | ||
|
|
251ac91b31 | ||
|
|
41d458404a | ||
|
|
eeb56e6b39 | ||
|
|
2043072d9c | ||
|
|
487ec8d410 | ||
|
|
75bb6bdb11 | ||
|
|
a2ef8b39d3 | ||
|
|
03495bfcf8 | ||
|
|
7f6b0f409c | ||
|
|
ec682693c1 | ||
|
|
f8f93c1443 | ||
|
|
fbfb93af64 | ||
|
|
0ac6b72783 | ||
|
|
e8a82fa683 | ||
|
|
a07545adc4 | ||
|
|
482f3cbaa2 | ||
|
|
a28f7ad531 | ||
|
|
953a401366 | ||
|
|
59f4a3729a | ||
|
|
357ede3664 | ||
|
|
e767694b82 | ||
|
|
4c7953c2d1 | ||
|
|
6c4634bc90 | ||
|
|
f0a2c0c465 | ||
|
|
01f504ca4b | ||
|
|
15e00edda2 | ||
|
|
21b53c06d1 | ||
|
|
e03797e882 | ||
|
|
43c6573ca4 | ||
|
|
f367bc8bce | ||
|
|
63c2cdb712 | ||
|
|
9b4c388585 | ||
|
|
576cebd56a | ||
|
|
86da22fb78 | ||
|
|
27e32b1633 | ||
|
|
2264afda84 | ||
|
|
792d2a8258 | ||
|
|
7abc19dc03 | ||
|
|
074bddd48b | ||
|
|
6cea81bc2c | ||
|
|
68b9fa86d7 | ||
|
|
d44964d923 | ||
|
|
52998539bc | ||
|
|
88acdb8172 | ||
|
|
0857f3aa82 | ||
|
|
0979371c45 | ||
|
|
96071aa492 | ||
|
|
e5f1553232 | ||
|
|
06c6011229 | ||
|
|
cbe97cc40d | ||
|
|
dc1ae3185a | ||
|
|
8e4a304626 | ||
|
|
5372dce0c2 | ||
|
|
bc67e2c584 | ||
|
|
e15099eb7b | ||
|
|
5f8f396e9b | ||
|
|
841e59e8c0 | ||
|
|
b55ca3d50d | ||
|
|
d7c9076228 | ||
|
|
7a292bc4c6 | ||
|
|
9213f6e533 | ||
|
|
5685203b7b | ||
|
|
22dda4a8ed | ||
|
|
eb22145f49 | ||
|
|
5d92f56bed | ||
|
|
d42d1855c2 | ||
|
|
0a81a41729 | ||
|
|
229f4808c7 | ||
|
|
167753aa3f | ||
|
|
dbbd5dba35 | ||
|
|
33757c3c94 | ||
|
|
7438e56f94 | ||
|
|
47416230b1 | ||
|
|
6a854ebd0d | ||
|
|
8c6bcddbab | ||
|
|
effa31e761 | ||
|
|
7792d7a1a7 | ||
|
|
8261dc39d9 | ||
|
|
9ed7049736 | ||
|
|
5a70066e5b | ||
|
|
6be8b478c3 | ||
|
|
34a1220035 | ||
|
|
c97cfe9f5b | ||
|
|
01858754ed | ||
|
|
c388dd98d6 | ||
|
|
38344e035f | ||
|
|
14d2f2c06e | ||
|
|
06fa2fc160 | ||
|
|
a9c22d787a | ||
|
|
1e287b6b30 | ||
|
|
152e9ecf3b | ||
|
|
2e0c19c915 | ||
|
|
4d6931620b | ||
|
|
d62fc91996 | ||
|
|
3ed05dbbb1 | ||
|
|
97c9178785 | ||
|
|
2fde4f3e50 | ||
|
|
b31547c903 | ||
|
|
2d56323012 | ||
|
|
63fefb8ea5 | ||
|
|
e4ec5d0009 | ||
|
|
a5e4ce3e48 | ||
|
|
2900ce33b5 | ||
|
|
ea5bf89955 | ||
|
|
109b77ea75 | ||
|
|
333315569c | ||
|
|
995e149256 | ||
|
|
971b0f3546 | ||
|
|
ab9603b28d | ||
|
|
adfd5853cc | ||
|
|
cc54c90ddc | ||
|
|
6a4071cdc2 | ||
|
|
e92be14df8 | ||
|
|
e9be1a91b3 | ||
|
|
7db3b96d2d | ||
|
|
429ad2a71a | ||
|
|
0b1598e9ef | ||
|
|
8d04b91932 | ||
|
|
665094d94b | ||
|
|
919d126af1 | ||
|
|
abd5df1729 | ||
|
|
3243ad91e8 | ||
|
|
c10d451f4d | ||
|
|
9c4a61850e | ||
|
|
5e3fe24412 | ||
|
|
7b81ca6b52 | ||
|
|
8c9ea4cde2 | ||
|
|
0ac72cb6a3 | ||
|
|
c4b089cf15 | ||
|
|
85e1bbf4bc | ||
|
|
032f111c8b | ||
|
|
e38528888a | ||
|
|
7c9a6d354e | ||
|
|
3ec90803ac | ||
|
|
c22473f15d | ||
|
|
55c064de28 | ||
|
|
06ce057920 | ||
|
|
76b4bd6d12 | ||
|
|
b5541aab55 | ||
|
|
a7160ac513 | ||
|
|
02d0301c9f | ||
|
|
a6cdf20c18 | ||
|
|
ad9a413892 | ||
|
|
1fddd2c29b | ||
|
|
2f75067569 | ||
|
|
6e8a00f215 | ||
|
|
702773e5fb | ||
|
|
4dfbd850ff | ||
|
|
b039e659af | ||
|
|
d16d44dda0 | ||
|
|
1c19389ba6 | ||
|
|
02fdff674c | ||
|
|
fd8b6a23a6 | ||
|
|
9e3a51d078 | ||
|
|
6f7a2ad7c7 | ||
|
|
499d656949 | ||
|
|
ba68f09026 | ||
|
|
3bdeeb73ef | ||
|
|
19868a800f | ||
|
|
df908ee872 | ||
|
|
84715ff4b9 | ||
|
|
5792195073 | ||
|
|
fb5662a449 | ||
|
|
6b2ad3db80 | ||
|
|
f1a969f0c3 | ||
|
|
f18320a152 | ||
|
|
bfcde59199 | ||
|
|
f23e9e70cd | ||
|
|
06fcd31da0 | ||
|
|
7e4fe99b1e | ||
|
|
159ea3d64d | ||
|
|
1f55c13bc4 | ||
|
|
d68b111d06 | ||
|
|
41f5a02204 | ||
|
|
7647576863 | ||
|
|
f11ba18df2 | ||
|
|
cf8587e401 | ||
|
|
7dd35ef749 | ||
|
|
41e570ddce | ||
|
|
a452131b50 | ||
|
|
4e49ca55ab | ||
|
|
c3f4474401 | ||
|
|
3d3eff86f3 | ||
|
|
fc9f154ac1 | ||
|
|
4aa0bca4dc | ||
|
|
905da6f083 | ||
|
|
86c7f29183 | ||
|
|
b0c99f876e | ||
|
|
bf5fcdc860 | ||
|
|
b58a1508fa | ||
|
|
4d527e81fa | ||
|
|
7803580aa6 | ||
|
|
32654db366 | ||
|
|
cd340cfab3 | ||
|
|
f32866b2ff | ||
|
|
1cd1dc091e | ||
|
|
0493656e86 | ||
|
|
66fd504c4d | ||
|
|
be4dd2b52f | ||
|
|
707d059766 | ||
|
|
f08c755ae6 | ||
|
|
dbbfdd4e4b | ||
|
|
f967fb40bf | ||
|
|
74e0f846cb | ||
|
|
303a4d26e5 | ||
|
|
119888653c | ||
|
|
a9f42c08f9 | ||
|
|
e79adc9d31 | ||
|
|
5a9056cd93 | ||
|
|
012c36ab5a | ||
|
|
5c4574f9aa | ||
|
|
a424775884 | ||
|
|
d6b1388741 | ||
|
|
796c6cae4e | ||
|
|
1a8064d6d9 | ||
|
|
43648924c3 | ||
|
|
bf2140e74d | ||
|
|
a1119266c1 | ||
|
|
a0f00c0eca | ||
|
|
d358954a84 | ||
|
|
aee00bdfb5 | ||
|
|
cf324b0fa1 | ||
|
|
b314dc224d | ||
|
|
1bbd62498e | ||
|
|
f3c3b1c04b | ||
|
|
069f98b253 | ||
|
|
dfd0503eae | ||
|
|
c629b2e87e | ||
|
|
7c8462abd1 | ||
|
|
95a6a0bde7 | ||
|
|
bba328fac5 | ||
|
|
41362349f3 | ||
|
|
12e3499b6d | ||
|
|
9576011011 | ||
|
|
155b34c1aa | ||
|
|
982ffe9ebe | ||
|
|
0251ecaeab | ||
|
|
372a27d645 | ||
|
|
72b4a061f3 | ||
|
|
29198efabe | ||
|
|
50aa51f93a | ||
|
|
79ccc81a86 | ||
|
|
3f0fdbb597 | ||
|
|
ea57bd8f03 | ||
|
|
bdba5b8403 | ||
|
|
58cc6ca9c0 | ||
|
|
e5996b440d | ||
|
|
ad9d03fd85 | ||
|
|
4de160ce20 | ||
|
|
fc8c8ce6e7 | ||
|
|
ddbb7f07c8 | ||
|
|
a5a04929fb | ||
|
|
1e29c59bcc | ||
|
|
b6abdc3845 | ||
|
|
77b8657fcc | ||
|
|
2fadd8bb62 | ||
|
|
60df2dd5d0 | ||
|
|
66b529b345 | ||
|
|
1304172a93 | ||
|
|
1315d4604d | ||
|
|
a31af31328 | ||
|
|
26c3c7d8f9 | ||
|
|
0650d7c7eb | ||
|
|
068f95ad2d | ||
|
|
f4fbf7c9ca | ||
|
|
843d6497b2 | ||
|
|
747c167658 | ||
|
|
fca2c5dba0 | ||
|
|
e12bc7f07c | ||
|
|
dc6ae51cab | ||
|
|
baa70d8ec9 | ||
|
|
c93b338bdd | ||
|
|
c0472aa0ec | ||
|
|
09552cfd73 | ||
|
|
003fec509c | ||
|
|
773a82d87f | ||
|
|
286c29d6fb | ||
|
|
969b0a3922 | ||
|
|
f8b2eacf99 | ||
|
|
6140ac6864 | ||
|
|
c6c2834e03 | ||
|
|
856545a1db | ||
|
|
e2d607f6c7 | ||
|
|
66da4e0657 | ||
|
|
b37390bb5a | ||
|
|
829dc8cceb | ||
|
|
13cc2c39f5 | ||
|
|
66ea3b271c | ||
|
|
d293b58a20 | ||
|
|
ce093b2bf3 | ||
|
|
e4404efe5a | ||
|
|
5ce270f1de | ||
|
|
af43b067a0 | ||
|
|
34b44d1fee | ||
|
|
595ceaac37 | ||
|
|
daf5834e8e | ||
|
|
0d8658a039 | ||
|
|
095e004d01 | ||
|
|
0acabee7f6 | ||
|
|
76fbcffb60 | ||
|
|
a0a62d7ead | ||
|
|
c5038ea6a5 | ||
|
|
a5120903eb | ||
|
|
00b286a08a | ||
|
|
24a9759353 | ||
|
|
1b56f6f46d | ||
|
|
2a8084d569 | ||
|
|
6ff29f9d4f | ||
|
|
c4d3e79193 | ||
|
|
7cd3f21e6b | ||
|
|
4a0aaf0786 | ||
|
|
9c3835524c | ||
|
|
549351bb8a | ||
|
|
b650b89682 | ||
|
|
74e6b19f83 | ||
|
|
2e684028de | ||
|
|
c54d87a472 | ||
|
|
4304245c1b | ||
|
|
6165931afa | ||
|
|
1d1fd3bcaf | ||
|
|
23581333e6 | ||
|
|
e5fa3d887f | ||
|
|
583fa7bb0a | ||
|
|
fe0db53842 | ||
|
|
76c0ada1e1 | ||
|
|
92f49e9194 | ||
|
|
44c8057b5f | ||
|
|
0ad837f595 | ||
|
|
bd2103c746 | ||
|
|
9c18d2ddb0 | ||
|
|
1245a8c151 | ||
|
|
07113dc8ba | ||
|
|
a3420e6fa9 | ||
|
|
732836d9f8 | ||
|
|
87658f7b53 | ||
|
|
e7f51e5fb1 | ||
|
|
1ce5f70dd1 | ||
|
|
473635f401 | ||
|
|
5adf2657dd | ||
|
|
82cfff2990 | ||
|
|
4397b1c442 | ||
|
|
9e6a4a4737 | ||
|
|
41f344bbd3 | ||
|
|
a77cd50b2f | ||
|
|
73af020f98 | ||
|
|
bffb83c46e | ||
|
|
7031f37350 | ||
|
|
829dd74cb2 | ||
|
|
66e671985d | ||
|
|
5afcbcf0f3 | ||
|
|
9730579312 | ||
|
|
bfae14d035 | ||
|
|
b78fc73d19 | ||
|
|
709f8ae76c | ||
|
|
7aa06329d0 | ||
|
|
9d6a38c44c | ||
|
|
6ec5cee368 | ||
|
|
f2e9a68825 | ||
|
|
d88750e6b6 | ||
|
|
821358eda7 | ||
|
|
fce6e1f135 | ||
|
|
8f0bb3e676 | ||
|
|
262c70d967 | ||
|
|
da43ef7c2d | ||
|
|
7b60ab5df1 | ||
|
|
f6b961a64e | ||
|
|
f1ed988aa3 | ||
|
|
eea51bb604 | ||
|
|
9203126aa5 | ||
|
|
f90ba4712a | ||
|
|
3737a24096 | ||
|
|
d418f78352 | ||
|
|
25163998a0 | ||
|
|
dc546aaa4b | ||
|
|
5364d580c9 | ||
|
|
2a9a6347e3 | ||
|
|
cfdb56f314 | ||
|
|
b517e88db3 | ||
|
|
bb317aba8d | ||
|
|
644cc6647e | ||
|
|
72397ce23b | ||
|
|
c646d91527 | ||
|
|
a2b98d82e1 | ||
|
|
7b9415c088 | ||
|
|
cb7110f492 | ||
|
|
0c7af66490 | ||
|
|
496d1b914a | ||
|
|
d60a80c098 | ||
|
|
bb8b6d9d73 | ||
|
|
677b4cc5b0 | ||
|
|
be565ffab6 | ||
|
|
df6120e5f6 | ||
|
|
21de6f7da8 | ||
|
|
dbe39f9ce0 | ||
|
|
ab3de50d5e | ||
|
|
c545bd2139 | ||
|
|
6a1c64fbdd | ||
|
|
b75809ed61 | ||
|
|
ecaf228e5c | ||
|
|
6d015ae8fc | ||
|
|
233150d93f | ||
|
|
7af8c77a52 | ||
|
|
a957e7bfa1 | ||
|
|
cee4c8ce8c | ||
|
|
96bf814d8c | ||
|
|
7ddc422788 | ||
|
|
e652fc2825 | ||
|
|
a49fa3f8d0 | ||
|
|
cd452a2f91 | ||
|
|
4f89f603ae | ||
|
|
11dc2c5e1d | ||
|
|
6fec3c15ca | ||
|
|
938c47480f | ||
|
|
3811d19298 | ||
|
|
83a3ab6b6f | ||
|
|
d66a9af6a3 | ||
|
|
adc90d3a86 | ||
|
|
ebbd015c5c | ||
|
|
4ab73b36b2 | ||
|
|
130e07a422 | ||
|
|
8f47bb367e | ||
|
|
0c3cb60135 | ||
|
|
9eae8fca5d | ||
|
|
882a217074 | ||
|
|
e465fce201 | ||
|
|
d41542c64b | ||
|
|
199818bd6c | ||
|
|
fe66c7ca30 | ||
|
|
e9177e4af3 | ||
|
|
d15a6c5933 | ||
|
|
25ab9325e7 | ||
|
|
19f9378b98 | ||
|
|
785bc7a14f | ||
|
|
1a1fe85428 | ||
|
|
0000d2e558 | ||
|
|
9ffd1ed4ce | ||
|
|
3d014864e2 | ||
|
|
1d22841811 | ||
|
|
a1cdda833f | ||
|
|
ad6db92690 | ||
|
|
e8ff9d8e50 | ||
|
|
795769c636 | ||
|
|
267a39d943 | ||
|
|
3624bd3d22 | ||
|
|
bc12dbbb38 | ||
|
|
eb8a008a8f | ||
|
|
c4d9aa1a21 | ||
|
|
6ae809ed40 | ||
|
|
311e2aab3f | ||
|
|
438dfbdb83 | ||
|
|
b2ce760cf4 | ||
|
|
b1ba209696 | ||
|
|
cb3e529b1e | ||
|
|
717f647418 | ||
|
|
98e7418187 | ||
|
|
fe05bf48b1 | ||
|
|
d2dd8f54e2 | ||
|
|
7726ee4b16 | ||
|
|
ba9bbe0221 | ||
|
|
4c3dd82d84 | ||
|
|
44e911b5b7 | ||
|
|
a7a16df9d0 | ||
|
|
382e0abefd | ||
|
|
6fdefe5b90 | ||
|
|
4788dd8e2e | ||
|
|
1cc5f221f3 | ||
|
|
93251bfba0 | ||
|
|
18b79508b8 | ||
|
|
4de5ed1613 | ||
|
|
0baaddbe98 | ||
|
|
8729c46169 | ||
|
|
09f81fe7c3 | ||
|
|
1876e5b7c0 | ||
|
|
355ec76257 | ||
|
|
b50fb34e71 | ||
|
|
de84d730ff | ||
|
|
c74d11e3d7 | ||
|
|
84cab5e6e7 | ||
|
|
c4fc972fec | ||
|
|
8cf809e231 | ||
|
|
94019a922e | ||
|
|
4f17c8d081 | ||
|
|
aaab753982 | ||
|
|
d6b2727f86 | ||
|
|
74a4f43946 | ||
|
|
1caf8b0f86 | ||
|
|
570b72a47b | ||
|
|
a5798a89ed | ||
|
|
3f3661a86f | ||
|
|
f7e2f9a401 | ||
|
|
2848a9b558 | ||
|
|
d4868991af | ||
|
|
e99d42404e | ||
|
|
3ba019c747 | ||
|
|
47429218bb | ||
|
|
8fe429346f | ||
|
|
5a4f9bf2e3 | ||
|
|
b91fc1b6b4 | ||
|
|
eafc150034 | ||
|
|
2877f1a268 | ||
|
|
1e893af775 | ||
|
|
d9f430a575 | ||
|
|
63abe87f36 | ||
|
|
368d649c8a | ||
|
|
5603464f39 | ||
|
|
655c79f39e | ||
|
|
565b231c03 | ||
|
|
62a9f180fa | ||
|
|
5ae77876a8 | ||
|
|
4ed2c2c74f | ||
|
|
955da582b6 | ||
|
|
11b07b950d | ||
|
|
8f70cfeda9 | ||
|
|
ce64271048 | ||
|
|
5cc4f3241d | ||
|
|
6815e138b4 | ||
|
|
a78a61d76f | ||
|
|
2eff3f34ed | ||
|
|
03687c1d62 | ||
|
|
febfe4e77f | ||
|
|
4d1aa134b5 | ||
|
|
5ec879860a | ||
|
|
f617468e04 | ||
|
|
b728af903c | ||
|
|
54f1999030 | ||
|
|
fd58f0b669 | ||
|
|
c5c67b706e | ||
|
|
be7a543e2c | ||
|
|
68f112d576 | ||
|
|
ec1395a304 | ||
|
|
beb0e474ee | ||
|
|
2b5fdcbbc5 | ||
|
|
295127d456 | ||
|
|
7dcfb13694 | ||
|
|
ee4046fe92 | ||
|
|
2a9cfeb9ea | ||
|
|
1147b8ea40 | ||
|
|
3f9119b39d | ||
|
|
35e8225abd | ||
|
|
bdbfbb7a14 | ||
|
|
f7d4be8d96 | ||
|
|
9fa8bd6438 | ||
|
|
02c8178f16 | ||
|
|
e637fbacae | ||
|
|
066544281f | ||
|
|
11be10d2c0 | ||
|
|
160969a758 | ||
|
|
622f78ebea | ||
|
|
aa67a5b095 | ||
|
|
af9ea0864c | ||
|
|
4e2a6d87c4 | ||
|
|
a465ecece9 | ||
|
|
575eb72182 | ||
|
|
3a973914d6 | ||
|
|
f568c07bbd | ||
|
|
2c9878fc3a | ||
|
|
27b1b1b005 | ||
|
|
130d7ab077 | ||
|
|
29f6b8a74a | ||
|
|
9779aaea33 | ||
|
|
ec25604a67 | ||
|
|
3668e81c5e | ||
|
|
d66b2423cb | ||
|
|
15cc78f0b6 | ||
|
|
06db4ddea2 | ||
|
|
6cfb90e99f | ||
|
|
d8be95a2a3 | ||
|
|
f82702872d | ||
|
|
3752c49ef0 | ||
|
|
fe65fa4988 | ||
|
|
1fe4c205a3 | ||
|
|
d4dc5e0f43 | ||
|
|
77944437ce | ||
|
|
c164bff758 | ||
|
|
aa2e3d954a | ||
|
|
de62b04728 | ||
|
|
d0bdb50f24 | ||
|
|
a8fecbc609 | ||
|
|
557fa483ff | ||
|
|
fc15d55df6 | ||
|
|
53573d7d94 | ||
|
|
bb3c177000 | ||
|
|
a3322b470f | ||
|
|
f8f408e7a9 | ||
|
|
baac1127d0 | ||
|
|
6f1328160c | ||
|
|
04cf902791 | ||
|
|
7a5b1c1a19 | ||
|
|
18d2d7da4a | ||
|
|
b461184797 | ||
|
|
4563b39305 | ||
|
|
c9d5674d5b | ||
|
|
486412635a | ||
|
|
8b23a1546a | ||
|
|
a901e4e369 | ||
|
|
804d9367d4 | ||
|
|
41d8adca95 | ||
|
|
059e8e5bb0 | ||
|
|
b3ee8ded96 | ||
|
|
cf3584ad15 | ||
|
|
a66973163f | ||
|
|
4502a8c8a1 | ||
|
|
9c902e4c2d | ||
|
|
f3eb36adcf | ||
|
|
7c246606c1 | ||
|
|
172c75029e | ||
|
|
6ae52da571 | ||
|
|
4ee9c68053 | ||
|
|
a15b4378a3 | ||
|
|
89fdd7f8dd | ||
|
|
c328be24b7 | ||
|
|
a73dc6dbf4 | ||
|
|
eee2a2657f | ||
|
|
12b8be7cb9 | ||
|
|
63c223ea5d | ||
|
|
2877fb4a2c | ||
|
|
d299c86633 | ||
|
|
6ce52092e8 | ||
|
|
b5926c1d21 | ||
|
|
9563238e9b | ||
|
|
fb9b1d76ca | ||
|
|
1739146599 | ||
|
|
ed20b39ab3 | ||
|
|
284fc05f15 | ||
|
|
07a07b6fa3 | ||
|
|
dc80b08969 | ||
|
|
a49a161f8d | ||
|
|
a6479ca50f | ||
|
|
0e607a55e7 | ||
|
|
8d305df0db | ||
|
|
c4b9f71357 | ||
|
|
394e506aea | ||
|
|
e19b26341b | ||
|
|
cfe1b13225 | ||
|
|
890c5ea1cd | ||
|
|
a87378d3b6 | ||
|
|
832fc08809 | ||
|
|
9a1ad6a5eb | ||
|
|
a90eafad24 | ||
|
|
ad14a82742 | ||
|
|
14e9d8ed9f | ||
|
|
0ac85fa70b | ||
|
|
c371de42b9 | ||
|
|
ccf147d6c1 | ||
|
|
7aa12b446f | ||
|
|
c293228102 | ||
|
|
5c4c9f721a | ||
|
|
057f86c1de | ||
|
|
cd52e3cbc2 | ||
|
|
24602e1259 | ||
|
|
8a098889fc | ||
|
|
5c3ace7c3e | ||
|
|
aa148455b7 | ||
|
|
98cf247f33 | ||
|
|
0cf16522d1 | ||
|
|
7b7c75f9e5 | ||
|
|
aefd255a3c | ||
|
|
1c5aa939fd | ||
|
|
3a0ff17be0 | ||
|
|
47829ae5cc | ||
|
|
bfa7b69aff | ||
|
|
2aaa959b5f | ||
|
|
ce2970b93a | ||
|
|
7b76970d10 | ||
|
|
9fd41882d2 | ||
|
|
ff2ea5de18 | ||
|
|
5147a42818 | ||
|
|
57552d8ca3 | ||
|
|
13713b2a76 | ||
|
|
36a14e4ee3 | ||
|
|
b4cc788b8c | ||
|
|
0f0e7512f3 | ||
|
|
1196b1a161 | ||
|
|
2c8c3be9ee | ||
|
|
5b79d51c22 | ||
|
|
da890dc293 | ||
|
|
93d0a1e73a | ||
|
|
f0a8c7d045 | ||
|
|
db8793777c | ||
|
|
c745484e65 | ||
|
|
da59379612 | ||
|
|
3ef2a41518 | ||
|
|
aa96f420c6 | ||
|
|
49e9e4ed0e | ||
|
|
f7b8163016 | ||
|
|
93769eacd3 | ||
|
|
59b0cc11df | ||
|
|
f32c275376 | ||
|
|
5404fc66ab | ||
|
|
1f53458af8 | ||
|
|
434c3e7f1d | ||
|
|
500b119f3d | ||
|
|
4b87259c1b | ||
|
|
503dec34ef | ||
|
|
d1e9fe50d2 | ||
|
|
d01e5fa838 | ||
|
|
a477c25e8c | ||
|
|
1bd20cd9e8 | ||
|
|
e49e95b037 | ||
|
|
6f59fed563 | ||
|
|
60b7f6c99d | ||
|
|
b92dfcc8d3 | ||
|
|
f6fd6dd053 | ||
|
|
79ad567dd5 | ||
|
|
fab1efb48c | ||
|
|
660eb76d93 | ||
|
|
461cd045c6 | ||
|
|
fee65d7a75 | ||
|
|
31f9971dbf | ||
|
|
62e7bf024a | ||
|
|
95f3d69cf9 | ||
|
|
89c0519f83 | ||
|
|
2704b82084 | ||
|
|
cf8632bbac | ||
|
|
d224297972 | ||
|
|
a4d11a630f | ||
|
|
2b4399f8b1 | ||
|
|
f17b8de907 | ||
|
|
d87296f3e8 | ||
|
|
be94cf1c6f | ||
|
|
cc04dc42dc | ||
|
|
070b61f08f | ||
|
|
7e5bd46dd3 | ||
|
|
228bbb9d81 | ||
|
|
b812a7b4c6 | ||
|
|
891a366f73 | ||
|
|
10116b3be8 | ||
|
|
a46a0f0882 | ||
|
|
a26a8a38f4 | ||
|
|
7435315d50 | ||
|
|
9b5f741e85 | ||
|
|
517822fdd2 | ||
|
|
1b93a9be88 | ||
|
|
783a66b348 | ||
|
|
976c3e9b59 | ||
|
|
f8ca971dae | ||
|
|
21bc8c24df | ||
|
|
30228214f7 | ||
|
|
ee3b3c4c56 | ||
|
|
462d706a63 | ||
|
|
ee0d460c8e | ||
|
|
cd15abe9d1 | ||
|
|
9f40467e24 | ||
|
|
d0b6593823 | ||
|
|
79fc821d8d | ||
|
|
d7fdb9a7e6 | ||
|
|
b74de51c18 | ||
|
|
44b466e072 | ||
|
|
5e5b471bb2 | ||
|
|
9c2565f64e | ||
|
|
e1d0a7cec3 | ||
|
|
b19ae8f465 | ||
|
|
cdff2c8e18 | ||
|
|
eb702f581b | ||
|
|
3d13fd56c5 | ||
|
|
6f51b49ef8 | ||
|
|
addc638856 | ||
|
|
42ae36bc28 | ||
|
|
c69f73ff9f | ||
|
|
ca5ae8a2e6 | ||
|
|
d967eb53de | ||
|
|
839f9f1bbe | ||
|
|
b754a152c6 | ||
|
|
e07cb2b9de | ||
|
|
a1f8bbb078 | ||
|
|
7909683f3b | ||
|
|
25f71913b7 | ||
|
|
34ddd2b7b1 | ||
|
|
d5fd90b2f3 | ||
|
|
b7c7000d0d | ||
|
|
551f6c4edd | ||
|
|
defd814750 | ||
|
|
3d517bbd2a | ||
|
|
78ab955fec | ||
|
|
dd13937bb6 | ||
|
|
66a1b63aa9 | ||
|
|
22c611bd1a | ||
|
|
c9bb1bf8ea | ||
|
|
9e489887cf | ||
|
|
9feb801bb9 | ||
|
|
c00b495933 | ||
|
|
d22eebe553 | ||
|
|
8bcbd82680 | ||
|
|
dfa617c439 | ||
|
|
48d1f0df89 | ||
|
|
b75cb7a12c | ||
|
|
332563e037 | ||
|
|
0cce97a4fe | ||
|
|
95a8e4be64 | ||
|
|
abcd6b8cb6 | ||
|
|
e8f21c9b6d | ||
|
|
e054078b11 | ||
|
|
6835a7f208 | ||
|
|
f59993b979 | ||
|
|
2290b8f680 | ||
|
|
2c54be651c | ||
|
|
e859a199df | ||
|
|
0a3682ad0b | ||
|
|
59abaeb5cd | ||
|
|
3e448435d3 | ||
|
|
a294bc3c5b | ||
|
|
b302ad3d49 | ||
|
|
82fc4b1e94 | ||
|
|
b4f1740380 | ||
|
|
031f85247c | ||
|
|
639cc6f73a | ||
|
|
09946cf1ba | ||
|
|
f4fa95e7cb | ||
|
|
100e29e35e | ||
|
|
4cbe471a83 | ||
|
|
8bece1f861 | ||
|
|
a3ca71ec01 | ||
|
|
e0543e8af5 | ||
|
|
c1eb80d01a | ||
|
|
a26121d97b | ||
|
|
043031a757 | ||
|
|
807aeebe4c | ||
|
|
8aa1a37aad | ||
|
|
4efa042f50 | ||
|
|
c7cb37e970 | ||
|
|
d34b207eab | ||
|
|
0e6fa6f6b8 | ||
|
|
38b87de53f | ||
|
|
aa5047a9e4 | ||
|
|
24b6ee0df9 | ||
|
|
1e79cc9cbe | ||
|
|
b3925df9c3 | ||
|
|
351795ac3a | ||
|
|
9c9c42d0df | ||
|
|
b6ad1bafc7 | ||
|
|
a5ca40f446 | ||
|
|
9ab54c5565 | ||
|
|
4341d96bde | ||
|
|
5fac47a26d | ||
|
|
e064f17346 | ||
|
|
afe10ba2a2 | ||
|
|
7cc3435ba8 | ||
|
|
541772313c | ||
|
|
3747494a09 | ||
|
|
f2b98d0dcc | ||
|
|
80471bf762 | ||
|
|
a06f63c110 | ||
|
|
0ae4478cd9 | ||
|
|
ae4e705e09 | ||
|
|
f5dcea9dbf | ||
|
|
2207309f8a | ||
|
|
2111e7ab5f | ||
|
|
d29abfdcaf | ||
|
|
a751c42cc5 | ||
|
|
6a3bc9865e | ||
|
|
4d5f7e4377 | ||
|
|
78b117fb78 | ||
|
|
ded63a1319 | ||
|
|
df3e4d1e9c | ||
|
|
b58fd80379 | ||
|
|
7f6e0f57d0 | ||
|
|
cae27678d8 | ||
|
|
48ff655bad | ||
|
|
2525ad4623 | ||
|
|
e7020017c5 | ||
|
|
eacebfad74 | ||
|
|
3bc2da5321 | ||
|
|
2d710d6bfd | ||
|
|
6532b7f32b | ||
|
|
7b41b92d99 | ||
|
|
dd557af84b | ||
|
|
59b9d0e030 | ||
|
|
b82eee4733 | ||
|
|
6a87487544 | ||
|
|
fcf5023845 | ||
|
|
c8adad6d8b | ||
|
|
737d3ffb98 | ||
|
|
b01e67bab1 | ||
|
|
8a70314f54 | ||
|
|
36ae6e5aba | ||
|
|
9db585cfeb | ||
|
|
c564611ba7 | ||
|
|
e187bcb85c | ||
|
|
be18ffe3b4 | ||
|
|
0d63dce4e2 | ||
|
|
26b30e1551 | ||
|
|
7fc58ac293 | ||
|
|
3a86cce8c1 | ||
|
|
37884d369f | ||
|
|
9246e653cd | ||
|
|
64283c8673 | ||
|
|
755002da9c | ||
|
|
31b8e8b437 | ||
|
|
0ec0de97e6 | ||
|
|
6c3ade5d89 | ||
|
|
980c5f9a34 | ||
|
|
e29b97b3ea | ||
|
|
ad2b699d2b | ||
|
|
471ca5f281 | ||
|
|
e82ddcff5d | ||
|
|
b9dcad89e8 | ||
|
|
993f43ef4a | ||
|
|
2b43308208 | ||
|
|
04a1ac3a76 | ||
|
|
990b8798bd | ||
|
|
b334a73a44 | ||
|
|
5d113d1c70 | ||
|
|
c14977aeab | ||
|
|
3e94838204 | ||
|
|
c0a0b8ca62 |
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,190 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Which rocBLAS kernel does the coarse multigrid GEMM get, and is a better
|
||||
// one available?
|
||||
//
|
||||
// The coarse operator issues a batched complex GEMM per stencil point with
|
||||
// M = K = nbasis, N = Nrhs and batch = the local coarse volume. Traces of a
|
||||
// production run show the complex-single kernel taking as long as the
|
||||
// complex-double one while reading half the bytes, and the kernel names say
|
||||
// why: the library picks a 64x64 macro tile for single and 16x16 for double,
|
||||
// while the output block is only nbasis x Nrhs. At nbasis 16 single
|
||||
// precision comes out absolutely slower than double.
|
||||
//
|
||||
// hipBLAS offers no way to override that choice, but rocBLAS's extended
|
||||
// entry point takes a solution index. This walks the indices, keeps the ones
|
||||
// the library accepts for this problem, times each, and prints them against
|
||||
// the default heuristic. No Grid, no MPI: one GPU, a few seconds.
|
||||
//
|
||||
// hipcc -O3 -std=c++17 RocblasSolutionSweep.cc -lrocblas -lamdhip64 \
|
||||
// -o RocblasSolutionSweep
|
||||
// ./RocblasSolutionSweep [nbasis] [nrhs] [batch]
|
||||
//
|
||||
// If the accepted solutions include one roughly twice the double-precision
|
||||
// rate, the application can select it by index and the tuning gap is ours to
|
||||
// close. If none does, the gap is the library's, and this output is the
|
||||
// report.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <sys/time.h>
|
||||
#include <hip/hip_runtime.h>
|
||||
#include <rocblas/rocblas.h>
|
||||
|
||||
static double usecond(void)
|
||||
{
|
||||
struct timeval tv; gettimeofday(&tv,NULL);
|
||||
return 1.0e6*tv.tv_sec + 1.0*tv.tv_usec;
|
||||
}
|
||||
#define HIP_CHECK(x) do { hipError_t e=(x); if(e!=hipSuccess){ printf("HIP error %s at line %d\n",hipGetErrorString(e),__LINE__); exit(1);} } while(0)
|
||||
|
||||
struct Result { int index; double gflops; double gbs; };
|
||||
|
||||
// One shape, one datatype, every solution index the library accepts.
|
||||
static void sweep(rocblas_handle handle,
|
||||
rocblas_datatype type, const char *name, size_t elembytes,
|
||||
int M,int N,int K,int BATCH,int maxindex,int ncall)
|
||||
{
|
||||
// Device data. Contents are irrelevant to the timing; the shapes are not.
|
||||
void *A,*B,*C;
|
||||
HIP_CHECK(hipMalloc(&A,(size_t)M*K*BATCH*elembytes));
|
||||
HIP_CHECK(hipMalloc(&B,(size_t)K*N*BATCH*elembytes));
|
||||
HIP_CHECK(hipMalloc(&C,(size_t)M*N*BATCH*elembytes));
|
||||
HIP_CHECK(hipMemset(A,0,(size_t)M*K*BATCH*elembytes));
|
||||
HIP_CHECK(hipMemset(B,0,(size_t)K*N*BATCH*elembytes));
|
||||
HIP_CHECK(hipMemset(C,0,(size_t)M*N*BATCH*elembytes));
|
||||
|
||||
// Pointer arrays, as the batched interface wants them, on the device.
|
||||
std::vector<void *> hA(BATCH),hB(BATCH),hC(BATCH);
|
||||
for(int b=0;b<BATCH;b++){
|
||||
hA[b] = (char *)A + (size_t)b*M*K*elembytes;
|
||||
hB[b] = (char *)B + (size_t)b*K*N*elembytes;
|
||||
hC[b] = (char *)C + (size_t)b*M*N*elembytes;
|
||||
}
|
||||
void **dA,**dB,**dC;
|
||||
HIP_CHECK(hipMalloc(&dA,BATCH*sizeof(void*)));
|
||||
HIP_CHECK(hipMalloc(&dB,BATCH*sizeof(void*)));
|
||||
HIP_CHECK(hipMalloc(&dC,BATCH*sizeof(void*)));
|
||||
HIP_CHECK(hipMemcpy(dA,&hA[0],BATCH*sizeof(void*),hipMemcpyHostToDevice));
|
||||
HIP_CHECK(hipMemcpy(dB,&hB[0],BATCH*sizeof(void*),hipMemcpyHostToDevice));
|
||||
HIP_CHECK(hipMemcpy(dC,&hC[0],BATCH*sizeof(void*),hipMemcpyHostToDevice));
|
||||
|
||||
// alpha = beta = 1: the stencil accumulates into C, so C is read as well
|
||||
// as written and the traffic below counts it twice.
|
||||
double alpha_d[2] = {1.0,0.0}, beta_d[2] = {1.0,0.0};
|
||||
float alpha_f[2] = {1.0f,0.0f}, beta_f[2] = {1.0f,0.0f};
|
||||
const void *alpha = (type==rocblas_datatype_f64_c) ? (const void *)alpha_d : (const void *)alpha_f;
|
||||
const void *beta = (type==rocblas_datatype_f64_c) ? (const void *)beta_d : (const void *)beta_f;
|
||||
|
||||
double flops = 8.0*M*N*K*BATCH; // complex madd
|
||||
double bytes = (double)elembytes*((double)M*K + (double)K*N + 2.0*(double)M*N)*BATCH;
|
||||
|
||||
auto run = [&](rocblas_gemm_algo algo,int solution)->double
|
||||
{
|
||||
rocblas_status st = rocblas_gemm_batched_ex(handle,
|
||||
rocblas_operation_none, rocblas_operation_none,
|
||||
M,N,K, alpha,
|
||||
(const void *)dA, type, M,
|
||||
(const void *)dB, type, K, beta,
|
||||
(const void *)dC, type, M,
|
||||
(void *)dC, type, M,
|
||||
BATCH, type, algo, solution, 0);
|
||||
if ( st != rocblas_status_success ) return -1.0;
|
||||
HIP_CHECK(hipDeviceSynchronize());
|
||||
double t0=usecond();
|
||||
for(int i=0;i<ncall;i++){
|
||||
rocblas_gemm_batched_ex(handle,
|
||||
rocblas_operation_none, rocblas_operation_none,
|
||||
M,N,K, alpha,
|
||||
(const void *)dA, type, M,
|
||||
(const void *)dB, type, K, beta,
|
||||
(const void *)dC, type, M,
|
||||
(void *)dC, type, M,
|
||||
BATCH, type, algo, solution, 0);
|
||||
}
|
||||
HIP_CHECK(hipDeviceSynchronize());
|
||||
return (usecond()-t0)/ncall;
|
||||
};
|
||||
|
||||
printf("\n=== %s : M=%d N=%d K=%d batch=%d ===\n",name,M,N,K,BATCH);
|
||||
|
||||
double tdef = run(rocblas_gemm_algo_standard,0);
|
||||
printf(" default heuristic : %8.1f Gflop/s %8.1f GB/s (%.3f ms)\n",
|
||||
flops/tdef/1.0e3, bytes/tdef/1.0e3, tdef/1.0e3);
|
||||
|
||||
// The extended call only accepts indices the library has itself listed for
|
||||
// this problem, so ask it for the list rather than guessing. If this does
|
||||
// not compile, check the argument order in rocblas.h: the enumeration call
|
||||
// has moved an argument or two between releases.
|
||||
rocblas_int nsol = 0;
|
||||
rocblas_status qst = rocblas_gemm_batched_ex_get_solutions(handle,
|
||||
rocblas_operation_none, rocblas_operation_none,
|
||||
M,N,K, alpha,
|
||||
(const void *)dA, type, M,
|
||||
(const void *)dB, type, K, beta,
|
||||
(const void *)dC, type, M,
|
||||
(void *)dC, type, M,
|
||||
BATCH, type, rocblas_gemm_flags_none,
|
||||
NULL, &nsol);
|
||||
std::vector<rocblas_int> sols;
|
||||
if ( qst == rocblas_status_success && nsol > 0 ) {
|
||||
sols.resize(nsol);
|
||||
rocblas_gemm_batched_ex_get_solutions(handle,
|
||||
rocblas_operation_none, rocblas_operation_none,
|
||||
M,N,K, alpha,
|
||||
(const void *)dA, type, M,
|
||||
(const void *)dB, type, K, beta,
|
||||
(const void *)dC, type, M,
|
||||
(void *)dC, type, M,
|
||||
BATCH, type, rocblas_gemm_flags_none,
|
||||
&sols[0], &nsol);
|
||||
} else {
|
||||
// Older library: fall back to probing indices.
|
||||
for(int s=1;s<=maxindex;s++) sols.push_back(s);
|
||||
}
|
||||
|
||||
std::vector<Result> ok;
|
||||
for(size_t i=0;i<sols.size();i++){
|
||||
double t = run(rocblas_gemm_algo_solution_index,sols[i]);
|
||||
if ( t < 0 ) continue; // library rejects it for this problem
|
||||
ok.push_back({(int)sols[i],flops/t/1.0e3,bytes/t/1.0e3});
|
||||
}
|
||||
printf(" library lists %d solution(s) for this problem\n",(int)sols.size());
|
||||
std::sort(ok.begin(),ok.end(),[](const Result &a,const Result &b){ return a.gflops>b.gflops; });
|
||||
|
||||
printf(" %d solutions accepted of %d tried\n",(int)ok.size(),maxindex);
|
||||
for(int i=0;i<(int)ok.size() && i<8;i++){
|
||||
printf(" solution %5d : %8.1f Gflop/s %8.1f GB/s\n",
|
||||
ok[i].index,ok[i].gflops,ok[i].gbs);
|
||||
}
|
||||
if ( ok.size() ) {
|
||||
printf(" best/default : %.2f\n", ok[0].gflops*tdef*1.0e3/flops);
|
||||
}
|
||||
|
||||
HIP_CHECK(hipFree(dA)); HIP_CHECK(hipFree(dB)); HIP_CHECK(hipFree(dC));
|
||||
HIP_CHECK(hipFree(A)); HIP_CHECK(hipFree(B)); HIP_CHECK(hipFree(C));
|
||||
}
|
||||
|
||||
int main(int argc,char **argv)
|
||||
{
|
||||
int nbasis = (argc>1) ? atoi(argv[1]) : 60;
|
||||
int nrhs = (argc>2) ? atoi(argv[2]) : 12;
|
||||
int batch = (argc>3) ? atoi(argv[3]) : 1024;
|
||||
int maxidx = (argc>4) ? atoi(argv[4]) : 400;
|
||||
|
||||
rocblas_handle handle;
|
||||
rocblas_create_handle(&handle);
|
||||
rocblas_set_pointer_mode(handle,rocblas_pointer_mode_host);
|
||||
|
||||
printf("rocBLAS solution sweep: the coarse stencil GEMM shape\n");
|
||||
printf("nbasis %d, Nrhs %d, batch %d, solution indices 1..%d\n",nbasis,nrhs,batch,maxidx);
|
||||
printf("(batch is the LOCAL coarse volume; x9 is the grouped call)\n");
|
||||
|
||||
sweep(handle,rocblas_datatype_f32_c,"complex single",8, nbasis,nrhs,nbasis,batch,maxidx,20);
|
||||
sweep(handle,rocblas_datatype_f64_c,"complex double",16,nbasis,nrhs,nbasis,batch,maxidx,20);
|
||||
|
||||
rocblas_destroy_handle(handle);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
mpicxx -qmkl=parallel -fsycl BatchBlasBench.cc -o BatchBlasBench -DGRID_SYCL
|
||||
@@ -0,0 +1,5 @@
|
||||
CXX=hipcc
|
||||
MPICXX=mpicxx
|
||||
CXXFLAGS="-fPIC -I{$ROCM_PATH}/include/ -I${MPICH_DIR}/include -L/lib64 -I/opt/cray/pe/mpich/8.1.28/ofi/gnu/12.3/include -DGRID_HIP"
|
||||
LDFLAGS="-L/lib64 -L${MPICH_DIR}/lib -lmpi -L${CRAY_MPICH_ROOTDIR}/gtl/lib -lmpi_gtl_hsa -lamdhip64 -lhipblas -lrocblas -lmpi_gnu_123"
|
||||
hipcc $CXXFLAGS $LDFLAGS BatchBlasBench.cc -o BatchBlasBench
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
mpicxx -qmkl=parallel -fsycl BatchBlasBench.cc -o BatchBlasBench -DGRID_SYCL
|
||||
@@ -0,0 +1,185 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## What This Is
|
||||
|
||||
Grid is a data-parallel C++ library for lattice QCD. It provides SIMD-vectorised lattice containers, MPI-based domain decomposition, GPU acceleration (CUDA/HIP/SYCL), and a full suite of QCD algorithms including HMC.
|
||||
|
||||
## Build
|
||||
|
||||
Uses GNU Autotools. The bootstrap step only needs to run once (or after `configure.ac` changes).
|
||||
|
||||
```bash
|
||||
./bootstrap.sh # downloads Eigen 3.4.0, generates configure
|
||||
mkdir build && cd build
|
||||
../configure [options]
|
||||
make -j$(nproc)
|
||||
make check # run root-level tests
|
||||
make install
|
||||
```
|
||||
|
||||
Key configure options:
|
||||
|
||||
| Option | Common values |
|
||||
|--------|---------------|
|
||||
| `--enable-simd=` | `AVX2`, `AVX512`, `KNL`, `A64FX`, `NEONv8`, `GPU` |
|
||||
| `--enable-comms=` | `mpi-auto`, `mpi3-auto`, `none` |
|
||||
| `--enable-accelerator=` | `cuda`, `hip`, `sycl` |
|
||||
| `--enable-shm=` | `shmopen`, `hugetlbfs`, `nvlink` |
|
||||
| `--enable-Nc=` | `3` (default), `2`, `4`, `5` |
|
||||
| `--with-gmp=`, `--with-mpfr=`, `--with-fftw=`, `--with-lime=` | paths to libs |
|
||||
| `--enable-hdf5`, `--enable-mkl`, `--enable-lapack` | optional features |
|
||||
|
||||
GPU builds additionally need `--enable-gen-simd-width=64` (sets 512-bit SIMD width for GPU warp/wavefront sizing) and `--enable-unified=no --enable-shm=nvlink` for multi-GPU runs.
|
||||
|
||||
To speed up compilation, `--disable-fermion-reps --disable-gparity` skips instantiating G-parity and higher-representation fermion operators.
|
||||
|
||||
Platform recipes from `README.md`:
|
||||
- **KNL**: `--enable-simd=KNL --enable-comms=mpi3-auto --enable-mkl`
|
||||
- **Skylake/Haswell**: `--enable-simd=AVX512` or `AVX2` + `--enable-comms=mpi3-auto`
|
||||
- **AMD EPYC**: `--enable-simd=AVX2 --enable-comms=mpi3`
|
||||
- **A64FX (Fugaku)**: `--enable-simd=A64FX --enable-comms=mpi3 --enable-shm=shmget` (see `SVE_README.txt`)
|
||||
|
||||
Complete, working `configure` invocations for specific HPC systems (Frontier/ROCm, Perlmutter/CUDA, Summit, SDCC-A100, etc.) live in `systems/<platform>/config-command`. These are the canonical references for production builds.
|
||||
|
||||
Required external libs: GMP, MPFR, OpenSSL, zlib.
|
||||
|
||||
### Use `systems/` for real machines
|
||||
|
||||
`systems/<machine>/` holds the known-good build for each production platform (`Frontier`, `Aurora`, `Perlmutter`, `Summit`, `Tursa`, `Lumi`, `Booster`, `Crusher`, `SDCC-*`, `mac-arm`, …). Each contains a `config-command` (the exact `../../configure` invocation) and a `sourceme.sh` (module loads and env). **Prefer copying/adapting these over hand-rolling configure flags** — they encode compiler workarounds, `LDFLAGS`, and shared-memory settings that are easy to get wrong. `systems/WorkArounds.txt` records known vendor bugs.
|
||||
|
||||
Note the GPU builds use `--enable-simd=GPU --enable-gen-simd-width=64`, so `Nsimd` is *not* 1 on device (it is `64/sizeof(scalar)`).
|
||||
|
||||
### Regenerating `Make.inc` — required after adding or deleting source files
|
||||
|
||||
`Make.inc` files are generated, not tracked in git (`.gitignore`d). `scripts/filelist` walks `Grid/`, `tests/*`, `benchmarks/`, `examples/`, and `HMC/` and writes the file lists and per-test `bin_PROGRAMS` rules. Every new `.cc`/`.h` in `Grid/`, and every new `Test_*.cc` / `Benchmark_*.cc` / `Example_*.cc`, is invisible to the build until you run:
|
||||
|
||||
```bash
|
||||
./scripts/filelist # from the source root, then re-run configure/make
|
||||
```
|
||||
|
||||
`bootstrap.sh` runs it for you on the first setup.
|
||||
|
||||
## Running Tests and Benchmarks
|
||||
|
||||
```bash
|
||||
# From build directory
|
||||
make check # root-level tests (Test_simd, Test_cshift, etc.)
|
||||
make -C tests/<subdir> tests # build tests in a subdirectory
|
||||
make tests # build all tests across all subdirectories
|
||||
./tests/core/Test_simd # run a single test binary directly
|
||||
mpirun -n 4 ./tests/core/Test_cshift --grid 16.16.16.16 --mpi 1.1.1.4
|
||||
```
|
||||
|
||||
`make check` is a thin smoke test — building a subdirectory with `make -C tests/<subdir> tests` and running the relevant binaries directly is the normal development loop. Test binaries take Grid's standard command-line arguments (`--grid`, `--mpi`, `--accelerator-threads`, `--threads`, `--debug-signals`, `--log`); see `Grid/util/Init.cc`.
|
||||
|
||||
Test subdirectories and their focus: `core` (SIMD, stencil, comms), `solver` (CG, GMRES, eigensolvers), `hmc` (MD integrators), `forces` (fermion forces), `lanczos`, `IO`, `smearing`, `sp2n`, `debug`.
|
||||
|
||||
Tests and benchmarks that need optional fermion representations are guarded by `disable_tests_without_instantiations.h` / `disable_benchmarks_without_instantiations.h`, so a `--disable-fermion-reps --disable-gparity` build silently compiles them to no-ops.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Layer stack (bottom to top)
|
||||
|
||||
1. **SIMD layer** (`Grid/simd/`) — platform-specific intrinsics wrapped into `vRealF`, `vComplexD`, etc. The SIMD width and layout are compile-time constants controlled by `--enable-simd`.
|
||||
|
||||
2. **Tensor layer** (`Grid/tensors/`) — Lorentz/colour/spin tensor algebra built on top of SIMD types. `iMatrix`, `iVector`, `iScalar` templates compose into QCD types like `ColourMatrix`, `SpinColourVector`.
|
||||
|
||||
3. **Lattice layer** (`Grid/lattice/`) — `Lattice<T>` container: a site-local tensor replicated across a distributed Cartesian grid. All arithmetic is site-parallel and expression-template-fused.
|
||||
|
||||
4. **Cartesian/comms layer** (`Grid/cartesian/`, `Grid/communicator/`) — `GridCartesian` holds the MPI topology and local/global geometry. `Grid/cshift/` implements nearest-neighbour halo exchange; `Grid/stencil/` is the optimised multi-hop stencil used by Dirac operators.
|
||||
|
||||
5. **Algorithm layer** (`Grid/algorithms/`) — iterative solvers (CG, GMRES, BiCGSTAB, mixed-precision), eigensolvers (Lanczos, LAPACK), FFT, smearing, and multigrid.
|
||||
|
||||
6. **QCD layer** (`Grid/qcd/`) — gauge and fermion actions, HMC integrators, observables.
|
||||
|
||||
### QCD subsystem (`Grid/qcd/`)
|
||||
|
||||
- `action/fermion/` — Wilson, Clover, DWF (Mobius), Staggered, twisted-mass, G-parity variants
|
||||
- `action/gauge/` — Wilson gauge, Symanzik, Iwasaki, DBW2, plaquette+rect
|
||||
- `representations/` — Fundamental, Adjoint, Two-index, Sp(2n)
|
||||
- `hmc/` — Leapfrog, OMF2/OMF4 integrators; pseudofermion refreshment; Metropolis accept/reject
|
||||
- `smearing/` — APE, Stout, HEX, gradient flow
|
||||
- `observables/` — Polyakov loop, plaquette, topological charge
|
||||
|
||||
### GPU acceleration and the view/memory-manager discipline
|
||||
### Multigrid (`Grid/algorithms/multigrid/`)
|
||||
|
||||
Aggregation-based algebraic multigrid for Wilson-type fermions. Key files: `GeneralCoarsenedMatrixMultiRHS.h` (`MultiGeneralCoarsenedOperator`, the coarse operator for any number of right-hand sides: coarsening by Fourier probing, batched-GEMM apply on a D+1 grid with rhs innermost and unvectorised), `MultiRHSBlockProject.h` (in `deflation/`; the batched-GEMM transfer operators), `PVdagMMultiGrid.h` (three-level non-Hermitian PVdagM chain with dense bottom) and `HDCGMultiGrid.h` (two-level Hermitian HDCG chain) with their `*Params.h` (XML-serialisable parameters), `MrhsMultiGrid.h` (mrhs V-cycle, preconditioner interface, fp64/fp32 seam), `Smoothers.h`, `MultiGridIO.h`, `Aggregates.h` (near-null vector construction), `Geometry.h` (coarse stencils: max-norm-1 boxes of Manhattan radius 1, 2 or 4), `CoarsenedMatrix.h` (the original nearest-neighbour Wilson multigrid). `deprecated/` holds the superseded coarse operators and the Aggregation-based single-RHS ADEF-2, with class names prefixed `Deprecated` (`DeprecatedGeneralCoarsenedMatrix`, `DeprecatedMultiGeneralCoarsenedMatrix`, `DeprecatedTwoLevelADEF2`); nothing in the library uses them, only older drivers in `examples/` and `tests/debug/`. `MultiGrid.h` is the top-level include.
|
||||
|
||||
### GPU acceleration
|
||||
|
||||
GPU support is injected via macros in `Grid/threads/Accelerator.h` — `accelerator_for(i, n, nsimd, {...})`, `accelerator_forNB` (non-blocking, must be followed by `accelerator_barrier()`), `accelerator_for2dNB`, and `accelerator_inline`. On a CPU build these degrade to `thread_for` (OpenMP). Unified virtual memory is on by default (`--enable-unified=yes`); device-aware MPI (`--enable-accelerator-aware-mpi`) avoids device→host copies on transfers.
|
||||
|
||||
Lattice data is **not** directly addressable inside a kernel. You must open a view with the correct access mode so `Grid/allocator/MemoryManager.h` can move/mark the data:
|
||||
|
||||
```cpp
|
||||
autoView(out_v, out, AcceleratorWriteDiscard); // RAII; closes at end of scope
|
||||
autoView(in_v, in, AcceleratorRead);
|
||||
accelerator_for(ss, grid->oSites(), Nsimd, {
|
||||
coalescedWrite(out_v[ss], coalescedRead(in_v[ss]));
|
||||
});
|
||||
```
|
||||
|
||||
Modes are `AcceleratorRead/Write/WriteDiscard` and `CpuRead/Write/WriteDiscard`. Getting the mode wrong (e.g. `AcceleratorRead` on a field you write) produces stale-data bugs that only appear on GPU builds. Inside kernels use `coalescedRead`/`coalescedWrite` rather than raw `operator[]` — they map the SIMD lane onto `threadIdx.x` so accesses stay coalesced.
|
||||
|
||||
### Repo-local debugging skills (`skills/`)
|
||||
|
||||
`skills/` contains hard-won, Grid-specific playbooks written as invocable skill files. Consult them before debugging in these areas rather than reasoning from first principles:
|
||||
|
||||
| File | Covers |
|
||||
|---|---|
|
||||
| `gpu-memory-performance.md` | `acceleratorThreads()`, LambdaApply thread mapping, `coalescedRead` idiom, fused vs staged HBM access |
|
||||
| `gpu-runtime-correctness.md` | GPU runtime returning early from sync, silent wrong answers |
|
||||
| `communication-overlap.md` | 7-phase halo pipeline, per-packet events, host-staging vs GPU-direct RDMA |
|
||||
| `mpi-heterogeneous.md` | `MPI_Sendrecv` device-buffer aliasing, deterministic reductions |
|
||||
| `compiler-validation.md` | Isolating GPU compiler codegen bugs, minimal reproducers |
|
||||
| `correctness-verification.md` | Double-run fingerprinting, per-packet checksums, flight recorder |
|
||||
| `hang-diagnosis.md` | Diagnosing MPI/accelerator hangs |
|
||||
|
||||
The key loop macros (defined in `Grid/threads/Accelerator.h`) are:
|
||||
- `accelerator_for(iter, num, nsimd, {...})` — maps to CUDA/HIP kernel or OpenMP loop; `nsimd` is the innermost SIMD lane count
|
||||
- `accelerator_forNB(...)` — non-blocking variant (no implicit barrier)
|
||||
- `accelerator_for2dNB(iter1, num1, iter2, num2, nsimd, {...})` — 2D kernel launch
|
||||
- `thread_for(iter, num, {...})` — CPU OpenMP loop (never dispatches to GPU)
|
||||
|
||||
On CPU builds, `accelerator_for` aliases to `thread_for`.
|
||||
|
||||
### Solver patterns
|
||||
|
||||
`SchurRedBlack` (`Grid/algorithms/iterative/SchurRedBlack.h`) implements red-black (even/odd) preconditioning for fermion operators. Most production fermion solves use `SchurRedBlackDiagMooeeSolve` or similar wrappers that internally call a `ConjugateGradient` on the Schur complement.
|
||||
|
||||
Mixed-precision solvers (`ConjugateGradientMixedPrec`, `BiCGSTABMixedPrec`) drive a double-precision outer loop with single-precision inner solves.
|
||||
|
||||
### Memory and I/O
|
||||
|
||||
- `Grid/allocator/` — aligned/NUMA-aware allocators; caching allocator via `--enable-alloc-cache`
|
||||
- `Grid/parallelIO/` — distributed parallel reader/writer for ILDG (via LIME), SciDAC, and native binary formats
|
||||
- `Grid/serialisation/` — text, binary, HDF5, XML/JSON serialisation of arbitrary Grid objects
|
||||
|
||||
### Executables
|
||||
|
||||
- `HMC/` — production HMC driver programmes (e.g. `Mobius2p1f.cc`, `DWF_plus_DSDR_nf2plus1_Shamir_Gparity.cc`)
|
||||
- `benchmarks/` — `Benchmark_dwf`, `Benchmark_ITT`, `Benchmark_comms`, `Benchmark_memory_bandwidth`, … used to qualify a new machine
|
||||
- `examples/` — small, readable programmes (`Example_plaquette.cc`, `Example_Mobius_spectrum.cc`) that are the best starting point for learning the API
|
||||
|
||||
Each of these directories auto-builds every top-level `.cc` as its own binary via `scripts/filelist`.
|
||||
|
||||
Every programme is wrapped in `Grid_init(&argc, &argv)` / `Grid_finalize()` (`Grid/util/Init.h`).
|
||||
|
||||
## Key Conventions
|
||||
|
||||
- **C++17** is required throughout.
|
||||
- Template structure: most classes are templated on `<_FImpl>` (fermion impl) or `<Gimpl>` (gauge impl), which encode the representation and precision. Instantiation is controlled by `--enable-fermion-instantiations`.
|
||||
- **Tensor indices are positional, not labelled.** The `Grid/tensors/` arithmetic recurses structurally over the `iScalar`/`iVector`/`iMatrix` nest: each level defines only the {scalar,vector,matrix}² products at its own level, with element types resolved by automatic type deduction, so every colour/spin/lorentz combination composes from ~200 lines (versus the pre-C++11 QDP++/PETE approach of machine-generating every case). An index's meaning derives entirely from its nesting depth counted from the outside; `iScalar` is the identity/broadcast case at every level. Never insert or remove a nesting level casually — the multiplication tables contract by position.
|
||||
- **Multigrid coarsening deepens the tensor nest by one level.** A coarse site vector is `iVector<CComplex,nbasis>`, and `innerProduct` on it returns `iScalar<CComplex>` — one level deeper than the fine block scalar. So the block-inner-product scalar type gains one `iScalar` wrapper per MG level (fine: `vTComplex`; level 2: `iScalar<vTComplex>`; see `examples/Example_pvdagm_3level.cc`). When calling `blockInnerProduct`/`blockZAXPY`/`blockOrthogonalise` on coarse fields, the coarse scalar type must match `decltype(innerProduct(siteVector(),siteVector()))` exactly; a wrong depth fails to compile (no viable `operator=` deep in the instantiation chain) rather than mis-contracting.
|
||||
- **Grids are borrowed, never owned.** `conformable` is pointer identity, so every object that interoperates must hold the *same* `GridCartesian *`; a class that minted its own grid internally could never conform with anything else. Ownership is therefore not available, and lifetime is managed by scope discipline instead of reference counting: whoever creates a grid retains it beyond every object it handed a reference to. Anything *derived* from a grid inherits this — `~PaddedCell` dereferences its `unpadded_grid`, so a `PaddedCell` cannot even be **destroyed** after its parent grid, only used. Where a consumer must let go early, it offers an explicit hand-back (`MultiGeneralCoarsenedOperator::ReleaseGrid()`) to be called *before* the grid is destroyed.
|
||||
|
||||
- The `RealD`/`RealF`/`ComplexD`/`ComplexF` typedefs are used everywhere; avoid raw `double`/`float`.
|
||||
- Use `GRID_ASSERT(cond)` (defined in `Grid/GridStd.h`), not bare `assert` — it prints a Grid-formatted message and aborts cleanly under MPI.
|
||||
- Logging is stream-based, not macro-based: `std::cout << GridLogMessage << ... << std::endl;`. Channels declared in `Grid/log/Log.h` include `GridLogError`, `GridLogWarning`, `GridLogDebug`, `GridLogPerformance`, `GridLogIterative`, `GridLogSolver`, `GridLogHMC`, `GridLogComms`, `GridLogMemory`, `GridLogDslash`, `GridLogIRL`, `GridLogMG`. A subset is switched on at runtime with e.g. `--log Error,Warning,Message,Performance,Iterative,Integrator,Debug,Colours` (names given without the `GridLog` prefix).
|
||||
- Performance-critical paths use `GRID_TRACE(name)` from `Grid/perfmon/Tracing.h` (compiled out unless `--enable-tracing` selects a backend) and the `GridStopWatch` timers in `Grid/perfmon/Timer.h`.
|
||||
- Reductions across MPI ranks go through `GridBase::GlobalSum` / `GlobalMax`; never reduce with bare MPI calls inside library code.
|
||||
- Everything lives in `NAMESPACE_BEGIN(Grid)` / `NAMESPACE_END(Grid)` macros; follow the surrounding file rather than writing `namespace Grid { }`.
|
||||
- British spelling is used in identifiers and comments (`colour`, `neighbour`, `serialisation`).
|
||||
@@ -51,11 +51,13 @@ directory
|
||||
#pragma nv_diag_suppress cast_to_qualified_type
|
||||
//disables nvcc specific warning in many files
|
||||
#pragma nv_diag_suppress esa_on_defaulted_function_ignored
|
||||
#pragma nv_diag_suppress declared_but_not_referenced
|
||||
#pragma nv_diag_suppress extra_semicolon
|
||||
#else
|
||||
//disables nvcc specific warning in json.hpp
|
||||
#pragma diag_suppress unsigned_compare_with_zero
|
||||
#pragma diag_suppress cast_to_qualified_type
|
||||
#pragma diag_suppress declared_but_not_referenced
|
||||
//disables nvcc specific warning in many files
|
||||
#pragma diag_suppress esa_on_defaulted_function_ignored
|
||||
#pragma diag_suppress extra_semicolon
|
||||
|
||||
+4
-1
@@ -54,11 +54,14 @@ Author: paboyle <paboyle@ph.ed.ac.uk>
|
||||
#include <Grid/serialisation/Serialisation.h>
|
||||
#include <Grid/util/Sha.h>
|
||||
#include <Grid/communicator/Communicator.h>
|
||||
#include <Grid/cartesian/Cartesian.h>
|
||||
#include <Grid/communicator/RingAllReduce.h>
|
||||
#include <Grid/cartesian/Cartesian.h>
|
||||
#include <Grid/perfmon/HostMemory.h>
|
||||
#include <Grid/tensors/Tensors.h>
|
||||
#include <Grid/lattice/Lattice.h>
|
||||
#include <Grid/cshift/Cshift.h>
|
||||
#include <Grid/stencil/Stencil.h>
|
||||
#include <Grid/stencil/GeneralLocalStencil.h>
|
||||
#include <Grid/parallelIO/BinaryIO.h>
|
||||
#include <Grid/algorithms/Algorithms.h>
|
||||
NAMESPACE_CHECK(GridCore)
|
||||
|
||||
+39
-4
@@ -1,9 +1,17 @@
|
||||
#ifndef GRID_STD_H
|
||||
#define GRID_STD_H
|
||||
|
||||
///////////////////
|
||||
// Grid config
|
||||
///////////////////
|
||||
#include "Config.h"
|
||||
|
||||
///////////////////
|
||||
// Std C++ dependencies
|
||||
///////////////////
|
||||
#define _NBACKTRACE (256)
|
||||
extern void * Grid_backtrace_buffer[_NBACKTRACE];
|
||||
|
||||
#include <cassert>
|
||||
#include <complex>
|
||||
#include <memory>
|
||||
@@ -15,7 +23,9 @@
|
||||
#include <random>
|
||||
#include <functional>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <strings.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
@@ -23,11 +33,36 @@
|
||||
#include <sys/time.h>
|
||||
#include <chrono>
|
||||
#include <zlib.h>
|
||||
#ifdef HAVE_EXECINFO_H
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
|
||||
void GridAbort(void);
|
||||
|
||||
#define ASSLOG(A) ::write(STDERR_FILENO,A,::strlen(A));
|
||||
#ifdef HAVE_EXECINFO_H
|
||||
#define GRID_ASSERT(b) if(!(b)) { \
|
||||
fflush(stdout); \
|
||||
ASSLOG(" GRID_ASSERT failure: "); \
|
||||
ASSLOG(__FILE__); \
|
||||
ASSLOG(" : "); \
|
||||
ASSLOG(#b); \
|
||||
ASSLOG(" : "); \
|
||||
int symbols = backtrace(Grid_backtrace_buffer,_NBACKTRACE); \
|
||||
backtrace_symbols_fd(Grid_backtrace_buffer,symbols,STDERR_FILENO); \
|
||||
GridAbort(); \
|
||||
};
|
||||
#else
|
||||
#define GRID_ASSERT(b) if(!(b)) { \
|
||||
ASSLOG(" GRID_ASSERT failure: "); \
|
||||
ASSLOG(__FILE__); \
|
||||
ASSLOG(" : "); \
|
||||
ASSLOG(#b); \
|
||||
ASSLOG(" : "); \
|
||||
GridAbort(); \
|
||||
};
|
||||
#endif
|
||||
|
||||
///////////////////
|
||||
// Grid config
|
||||
///////////////////
|
||||
#include "Config.h"
|
||||
|
||||
#ifdef TOFU
|
||||
#undef GRID_COMMS_THREADS
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
#pragma push_macro("__SYCL_DEVICE_ONLY__")
|
||||
#undef __SYCL_DEVICE_ONLY__
|
||||
#define EIGEN_DONT_VECTORIZE
|
||||
//#undef EIGEN_USE_SYCL
|
||||
#undef EIGEN_USE_SYCL
|
||||
#define __SYCL__REDEFINE__
|
||||
#endif
|
||||
|
||||
|
||||
+11
-7
@@ -54,21 +54,25 @@ Version.h: version-cache
|
||||
include Make.inc
|
||||
include Eigen.inc
|
||||
|
||||
extra_sources+=$(WILS_FERMION_FILES)
|
||||
extra_sources+=$(STAG_FERMION_FILES)
|
||||
if BUILD_FERMION_INSTANTIATIONS
|
||||
extra_sources+=$(WILS_FERMION_FILES)
|
||||
extra_sources+=$(STAG_FERMION_FILES)
|
||||
if BUILD_ZMOBIUS
|
||||
extra_sources+=$(ZWILS_FERMION_FILES)
|
||||
extra_sources+=$(ZWILS_FERMION_FILES)
|
||||
endif
|
||||
if BUILD_GPARITY
|
||||
extra_sources+=$(GP_FERMION_FILES)
|
||||
extra_sources+=$(GP_FERMION_FILES)
|
||||
endif
|
||||
if BUILD_FERMION_REPS
|
||||
extra_sources+=$(ADJ_FERMION_FILES)
|
||||
extra_sources+=$(TWOIND_FERMION_FILES)
|
||||
extra_sources+=$(ADJ_FERMION_FILES)
|
||||
extra_sources+=$(TWOIND_FERMION_FILES)
|
||||
endif
|
||||
if BUILD_SP
|
||||
extra_sources+=$(SP_FERMION_FILES)
|
||||
extra_sources+=$(SP_TWOIND_FERMION_FILES)
|
||||
if BUILD_FERMION_REPS
|
||||
extra_sources+=$(SP_TWOIND_FERMION_FILES)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
lib_LIBRARIES = libGrid.a
|
||||
|
||||
@@ -29,6 +29,7 @@ directory
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <exception>
|
||||
#include <cassert>
|
||||
|
||||
#define NAMESPACE_BEGIN(A) namespace A {
|
||||
@@ -36,3 +37,7 @@ directory
|
||||
#define GRID_NAMESPACE_BEGIN NAMESPACE_BEGIN(Grid)
|
||||
#define GRID_NAMESPACE_END NAMESPACE_END(Grid)
|
||||
#define NAMESPACE_CHECK(x) struct namespaceTEST##x {}; static_assert(std::is_same<namespaceTEST##x, ::namespaceTEST##x>::value,"Not in :: at" );
|
||||
|
||||
#define EXCEPTION_CHECK_BEGIN(A) try {
|
||||
#define EXCEPTION_CHECK_END(A) } catch ( std::exception e ) { BACKTRACEFP(stderr); std::cerr << __PRETTY_FUNCTION__ << " : " <<__LINE__<< " Caught exception "<<e.what()<<std::endl; throw; }
|
||||
|
||||
@@ -29,7 +29,11 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
|
||||
#ifndef GRID_ALGORITHMS_H
|
||||
#define GRID_ALGORITHMS_H
|
||||
|
||||
NAMESPACE_CHECK(blas);
|
||||
#include <Grid/algorithms/blas/BatchedBlas.h>
|
||||
|
||||
NAMESPACE_CHECK(algorithms);
|
||||
#include <Grid/algorithms/SplitOperator.h>
|
||||
#include <Grid/algorithms/SparseMatrix.h>
|
||||
#include <Grid/algorithms/LinearOperator.h>
|
||||
#include <Grid/algorithms/Preconditioner.h>
|
||||
@@ -44,7 +48,13 @@ NAMESPACE_CHECK(SparseMatrix);
|
||||
#include <Grid/algorithms/approx/RemezGeneral.h>
|
||||
#include <Grid/algorithms/approx/ZMobius.h>
|
||||
NAMESPACE_CHECK(approx);
|
||||
#include <Grid/algorithms/iterative/Deflation.h>
|
||||
#include <Grid/algorithms/deflation/Deflation.h>
|
||||
#include <Grid/algorithms/deflation/MultiRHSBlockProject.h>
|
||||
#include <Grid/algorithms/deflation/MultiRHSDeflation.h>
|
||||
#include <Grid/algorithms/deflation/MultiRHSBlockCGLinalg.h>
|
||||
// Not really deflation, but useful
|
||||
#include <Grid/algorithms/blas/MomentumProject.h>
|
||||
NAMESPACE_CHECK(deflation);
|
||||
#include <Grid/algorithms/iterative/ConjugateGradient.h>
|
||||
NAMESPACE_CHECK(ConjGrad);
|
||||
#include <Grid/algorithms/iterative/BiCGSTAB.h>
|
||||
@@ -67,10 +77,11 @@ NAMESPACE_CHECK(BiCGSTAB);
|
||||
#include <Grid/algorithms/iterative/MixedPrecisionFlexibleGeneralisedMinimalResidual.h>
|
||||
#include <Grid/algorithms/iterative/ImplicitlyRestartedLanczos.h>
|
||||
#include <Grid/algorithms/iterative/PowerMethod.h>
|
||||
|
||||
#include <Grid/algorithms/iterative/AdefGeneric.h>
|
||||
#include <Grid/algorithms/iterative/AdefMrhs.h>
|
||||
NAMESPACE_CHECK(PowerMethod);
|
||||
#include <Grid/algorithms/CoarsenedMatrix.h>
|
||||
NAMESPACE_CHECK(CoarsendMatrix);
|
||||
#include <Grid/algorithms/multigrid/MultiGrid.h>
|
||||
NAMESPACE_CHECK(multigrid);
|
||||
#include <Grid/algorithms/FFT.h>
|
||||
|
||||
#endif
|
||||
+649
-235
@@ -1,6 +1,6 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./lib/Cshift.h
|
||||
|
||||
@@ -28,266 +28,680 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
|
||||
#ifndef _GRID_FFT_H_
|
||||
#define _GRID_FFT_H_
|
||||
|
||||
#ifdef GRID_CUDA
|
||||
#include <cufft.h>
|
||||
#endif
|
||||
|
||||
#ifdef GRID_HIP
|
||||
#include <hipfft/hipfft.h>
|
||||
#endif
|
||||
|
||||
#if !defined(GRID_CUDA) && !defined(GRID_HIP)
|
||||
#ifdef HAVE_FFTW
|
||||
#ifdef USE_MKL
|
||||
#if defined(USE_MKL) || defined(GRID_SYCL)
|
||||
#include <fftw/fftw3.h>
|
||||
#else
|
||||
#include <fftw3.h>
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
template<class scalar> struct FFTW { };
|
||||
|
||||
#ifdef HAVE_FFTW
|
||||
template<> struct FFTW<ComplexD> {
|
||||
public:
|
||||
|
||||
typedef fftw_complex FFTW_scalar;
|
||||
typedef fftw_plan FFTW_plan;
|
||||
|
||||
static FFTW_plan fftw_plan_many_dft(int rank, const int *n,int howmany,
|
||||
FFTW_scalar *in, const int *inembed,
|
||||
int istride, int idist,
|
||||
FFTW_scalar *out, const int *onembed,
|
||||
int ostride, int odist,
|
||||
int sign, unsigned flags) {
|
||||
return ::fftw_plan_many_dft(rank,n,howmany,in,inembed,istride,idist,out,onembed,ostride,odist,sign,flags);
|
||||
}
|
||||
|
||||
static void fftw_flops(const FFTW_plan p,double *add, double *mul, double *fmas){
|
||||
::fftw_flops(p,add,mul,fmas);
|
||||
}
|
||||
|
||||
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out) {
|
||||
::fftw_execute_dft(p,in,out);
|
||||
}
|
||||
inline static void fftw_destroy_plan(const FFTW_plan p) {
|
||||
::fftw_destroy_plan(p);
|
||||
}
|
||||
};
|
||||
|
||||
template<> struct FFTW<ComplexF> {
|
||||
public:
|
||||
|
||||
typedef fftwf_complex FFTW_scalar;
|
||||
typedef fftwf_plan FFTW_plan;
|
||||
|
||||
static FFTW_plan fftw_plan_many_dft(int rank, const int *n,int howmany,
|
||||
FFTW_scalar *in, const int *inembed,
|
||||
int istride, int idist,
|
||||
FFTW_scalar *out, const int *onembed,
|
||||
int ostride, int odist,
|
||||
int sign, unsigned flags) {
|
||||
return ::fftwf_plan_many_dft(rank,n,howmany,in,inembed,istride,idist,out,onembed,ostride,odist,sign,flags);
|
||||
}
|
||||
|
||||
static void fftw_flops(const FFTW_plan p,double *add, double *mul, double *fmas){
|
||||
::fftwf_flops(p,add,mul,fmas);
|
||||
}
|
||||
|
||||
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out) {
|
||||
::fftwf_execute_dft(p,in,out);
|
||||
}
|
||||
inline static void fftw_destroy_plan(const FFTW_plan p) {
|
||||
::fftwf_destroy_plan(p);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef FFTW_FORWARD
|
||||
#define FFTW_FORWARD (-1)
|
||||
#define FFTW_BACKWARD (+1)
|
||||
#define FFTW_ESTIMATE (0)
|
||||
#endif
|
||||
|
||||
class FFT {
|
||||
private:
|
||||
|
||||
GridCartesian *vgrid;
|
||||
GridCartesian *sgrid;
|
||||
|
||||
int Nd;
|
||||
double flops;
|
||||
double flops_call;
|
||||
uint64_t usec;
|
||||
|
||||
Coordinate dimensions;
|
||||
Coordinate processors;
|
||||
Coordinate processor_coor;
|
||||
|
||||
template<class scalar> struct FFTW {
|
||||
};
|
||||
|
||||
#ifdef GRID_HIP
|
||||
template<> struct FFTW<ComplexD> {
|
||||
public:
|
||||
|
||||
static const int forward=FFTW_FORWARD;
|
||||
static const int backward=FFTW_BACKWARD;
|
||||
|
||||
double Flops(void) {return flops;}
|
||||
double MFlops(void) {return flops/usec;}
|
||||
double USec(void) {return (double)usec;}
|
||||
|
||||
FFT ( GridCartesian * grid ) :
|
||||
vgrid(grid),
|
||||
Nd(grid->_ndimension),
|
||||
dimensions(grid->_fdimensions),
|
||||
processors(grid->_processors),
|
||||
processor_coor(grid->_processor_coor)
|
||||
{
|
||||
flops=0;
|
||||
usec =0;
|
||||
Coordinate layout(Nd,1);
|
||||
sgrid = new GridCartesian(dimensions,layout,processors,*grid);
|
||||
};
|
||||
|
||||
~FFT ( void) {
|
||||
delete sgrid;
|
||||
}
|
||||
|
||||
template<class vobj>
|
||||
void FFT_dim_mask(Lattice<vobj> &result,const Lattice<vobj> &source,Coordinate mask,int sign){
|
||||
|
||||
conformable(result.Grid(),vgrid);
|
||||
conformable(source.Grid(),vgrid);
|
||||
Lattice<vobj> tmp(vgrid);
|
||||
tmp = source;
|
||||
for(int d=0;d<Nd;d++){
|
||||
if( mask[d] ) {
|
||||
FFT_dim(result,tmp,d,sign);
|
||||
tmp=result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class vobj>
|
||||
void FFT_all_dim(Lattice<vobj> &result,const Lattice<vobj> &source,int sign){
|
||||
Coordinate mask(Nd,1);
|
||||
FFT_dim_mask(result,source,mask,sign);
|
||||
}
|
||||
|
||||
|
||||
template<class vobj>
|
||||
void FFT_dim(Lattice<vobj> &result,const Lattice<vobj> &source,int dim, int sign){
|
||||
#ifndef HAVE_FFTW
|
||||
assert(0);
|
||||
#else
|
||||
conformable(result.Grid(),vgrid);
|
||||
conformable(source.Grid(),vgrid);
|
||||
|
||||
int L = vgrid->_ldimensions[dim];
|
||||
int G = vgrid->_fdimensions[dim];
|
||||
|
||||
Coordinate layout(Nd,1);
|
||||
Coordinate pencil_gd(vgrid->_fdimensions);
|
||||
|
||||
pencil_gd[dim] = G*processors[dim];
|
||||
|
||||
// Pencil global vol LxLxGxLxL per node
|
||||
GridCartesian pencil_g(pencil_gd,layout,processors,*vgrid);
|
||||
|
||||
// Construct pencils
|
||||
typedef typename vobj::scalar_object sobj;
|
||||
typedef typename sobj::scalar_type scalar;
|
||||
|
||||
Lattice<sobj> pgbuf(&pencil_g);
|
||||
autoView(pgbuf_v , pgbuf, CpuWrite);
|
||||
|
||||
typedef typename FFTW<scalar>::FFTW_scalar FFTW_scalar;
|
||||
typedef typename FFTW<scalar>::FFTW_plan FFTW_plan;
|
||||
|
||||
int Ncomp = sizeof(sobj)/sizeof(scalar);
|
||||
int Nlow = 1;
|
||||
for(int d=0;d<dim;d++){
|
||||
Nlow*=vgrid->_ldimensions[d];
|
||||
}
|
||||
|
||||
int rank = 1; /* 1d transforms */
|
||||
int n[] = {G}; /* 1d transforms of length G */
|
||||
int howmany = Ncomp;
|
||||
int odist,idist,istride,ostride;
|
||||
idist = odist = 1; /* Distance between consecutive FT's */
|
||||
istride = ostride = Ncomp*Nlow; /* distance between two elements in the same FT */
|
||||
int *inembed = n, *onembed = n;
|
||||
|
||||
scalar div;
|
||||
if ( sign == backward ) div = 1.0/G;
|
||||
else if ( sign == forward ) div = 1.0;
|
||||
else assert(0);
|
||||
|
||||
typedef hipfftDoubleComplex FFTW_scalar;
|
||||
typedef hipfftHandle FFTW_plan;
|
||||
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
|
||||
FFTW_scalar *in, int *inembed,
|
||||
int istride, int idist,
|
||||
FFTW_scalar *out, int *onembed,
|
||||
int ostride, int odist,
|
||||
int sign, unsigned flags) {
|
||||
FFTW_plan p;
|
||||
auto rv = hipfftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,HIPFFT_Z2Z,howmany);
|
||||
GRID_ASSERT(rv==HIPFFT_SUCCESS);
|
||||
return p;
|
||||
}
|
||||
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
|
||||
hipfftResult rv;
|
||||
if ( sign == forward ) rv =hipfftExecZ2Z(p,in,out,HIPFFT_FORWARD);
|
||||
else rv =hipfftExecZ2Z(p,in,out,HIPFFT_BACKWARD);
|
||||
accelerator_barrier();
|
||||
GRID_ASSERT(rv==HIPFFT_SUCCESS);
|
||||
}
|
||||
inline static void fftw_destroy_plan(const FFTW_plan p) { hipfftDestroy(p); }
|
||||
};
|
||||
template<> struct FFTW<ComplexF> {
|
||||
public:
|
||||
static const int forward=FFTW_FORWARD;
|
||||
static const int backward=FFTW_BACKWARD;
|
||||
typedef hipfftComplex FFTW_scalar;
|
||||
typedef hipfftHandle FFTW_plan;
|
||||
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
|
||||
FFTW_scalar *in, int *inembed,
|
||||
int istride, int idist,
|
||||
FFTW_scalar *out, int *onembed,
|
||||
int ostride, int odist,
|
||||
int sign, unsigned flags) {
|
||||
FFTW_plan p;
|
||||
auto rv = hipfftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,HIPFFT_C2C,howmany);
|
||||
GRID_ASSERT(rv==HIPFFT_SUCCESS);
|
||||
return p;
|
||||
}
|
||||
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
|
||||
hipfftResult rv;
|
||||
if ( sign == forward ) rv =hipfftExecC2C(p,in,out,HIPFFT_FORWARD);
|
||||
else rv =hipfftExecC2C(p,in,out,HIPFFT_BACKWARD);
|
||||
accelerator_barrier();
|
||||
GRID_ASSERT(rv==HIPFFT_SUCCESS);
|
||||
}
|
||||
inline static void fftw_destroy_plan(const FFTW_plan p) { hipfftDestroy(p); }
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef GRID_CUDA
|
||||
template<> struct FFTW<ComplexD> {
|
||||
public:
|
||||
static const int forward=FFTW_FORWARD;
|
||||
static const int backward=FFTW_BACKWARD;
|
||||
typedef cufftDoubleComplex FFTW_scalar;
|
||||
typedef cufftHandle FFTW_plan;
|
||||
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
|
||||
FFTW_scalar *in, int *inembed,
|
||||
int istride, int idist,
|
||||
FFTW_scalar *out, int *onembed,
|
||||
int ostride, int odist,
|
||||
int sign, unsigned flags) {
|
||||
FFTW_plan p;
|
||||
cufftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,CUFFT_Z2Z,howmany);
|
||||
return p;
|
||||
}
|
||||
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
|
||||
if ( sign == forward ) cufftExecZ2Z(p,in,out,CUFFT_FORWARD);
|
||||
else cufftExecZ2Z(p,in,out,CUFFT_INVERSE);
|
||||
accelerator_barrier();
|
||||
}
|
||||
inline static void fftw_destroy_plan(const FFTW_plan p) { cufftDestroy(p); }
|
||||
};
|
||||
template<> struct FFTW<ComplexF> {
|
||||
public:
|
||||
static const int forward=FFTW_FORWARD;
|
||||
static const int backward=FFTW_BACKWARD;
|
||||
typedef cufftComplex FFTW_scalar;
|
||||
typedef cufftHandle FFTW_plan;
|
||||
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
|
||||
FFTW_scalar *in, int *inembed,
|
||||
int istride, int idist,
|
||||
FFTW_scalar *out, int *onembed,
|
||||
int ostride, int odist,
|
||||
int sign, unsigned flags) {
|
||||
FFTW_plan p;
|
||||
cufftPlanMany(&p,rank,n,n,istride,idist,n,ostride,odist,CUFFT_C2C,howmany);
|
||||
return p;
|
||||
}
|
||||
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
|
||||
if ( sign == forward ) cufftExecC2C(p,in,out,CUFFT_FORWARD);
|
||||
else cufftExecC2C(p,in,out,CUFFT_INVERSE);
|
||||
accelerator_barrier();
|
||||
}
|
||||
inline static void fftw_destroy_plan(const FFTW_plan p) { cufftDestroy(p); }
|
||||
};
|
||||
#endif
|
||||
|
||||
#if !defined(GRID_CUDA) && !defined(GRID_HIP)
|
||||
#ifdef HAVE_FFTW
|
||||
template<> struct FFTW<ComplexD> {
|
||||
public:
|
||||
typedef fftw_complex FFTW_scalar;
|
||||
typedef fftw_plan FFTW_plan;
|
||||
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
|
||||
FFTW_scalar *in, int *inembed,
|
||||
int istride, int idist,
|
||||
FFTW_scalar *out, int *onembed,
|
||||
int ostride, int odist,
|
||||
int sign, unsigned flags) {
|
||||
return ::fftw_plan_many_dft(rank,n,howmany,in,inembed,istride,idist,out,onembed,ostride,odist,sign,flags);
|
||||
}
|
||||
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
|
||||
::fftw_execute_dft(p,in,out);
|
||||
}
|
||||
inline static void fftw_destroy_plan(const FFTW_plan p) { ::fftw_destroy_plan(p); }
|
||||
};
|
||||
template<> struct FFTW<ComplexF> {
|
||||
public:
|
||||
typedef fftwf_complex FFTW_scalar;
|
||||
typedef fftwf_plan FFTW_plan;
|
||||
static FFTW_plan fftw_plan_many_dft(int rank, int *n,int howmany,
|
||||
FFTW_scalar *in, int *inembed,
|
||||
int istride, int idist,
|
||||
FFTW_scalar *out, int *onembed,
|
||||
int ostride, int odist,
|
||||
int sign, unsigned flags) {
|
||||
return ::fftwf_plan_many_dft(rank,n,howmany,in,inembed,istride,idist,out,onembed,ostride,odist,sign,flags);
|
||||
}
|
||||
inline static void fftw_execute_dft(const FFTW_plan p,FFTW_scalar *in,FFTW_scalar *out, int sign) {
|
||||
::fftwf_execute_dft(p,in,out);
|
||||
}
|
||||
inline static void fftw_destroy_plan(const FFTW_plan p) { ::fftwf_destroy_plan(p); }
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
|
||||
struct FFTbase {
|
||||
double flops;
|
||||
double flops_call;
|
||||
uint64_t usec;
|
||||
GridCartesian *_grid;
|
||||
|
||||
static const int forward = FFTW_FORWARD;
|
||||
static const int backward = FFTW_BACKWARD;
|
||||
|
||||
double Flops(void) { return flops; }
|
||||
double MFlops(void) { return flops / usec; }
|
||||
double USec(void) { return (double)usec; }
|
||||
|
||||
FFTbase(GridCartesian *grid) : _grid(grid), flops(0), flops_call(0), usec(0) {}
|
||||
};
|
||||
|
||||
// Barrel-shift gather, FFT execute, and insert. Called by both FFT and PlannedFFT.
|
||||
// The caller is responsible for plan acquisition and destruction.
|
||||
template<class vobj>
|
||||
static void FFT_dim_execute(
|
||||
Lattice<vobj> &result,
|
||||
const Lattice<vobj> &source,
|
||||
int dim, int sign,
|
||||
typename FFTW<typename vobj::scalar_type>::FFTW_plan p,
|
||||
GridCartesian *grid,
|
||||
double &flops, double &flops_call, uint64_t &usec)
|
||||
{
|
||||
typedef typename vobj::scalar_type scalar;
|
||||
typedef typename vobj::scalar_object sobj;
|
||||
typedef typename vobj::scalar_type scalar_type;
|
||||
typedef typename vobj::vector_type vector_type;
|
||||
typedef typename FFTW<scalar>::FFTW_scalar FFTW_scalar;
|
||||
|
||||
#if 0
|
||||
// ======================= ORIGINAL barrel-shift path =======================
|
||||
// Preserved for reference. Superseded by the transpose / all-to-all path
|
||||
// below (the single active path for ALL P): the barrel is a P-fold redundant
|
||||
// all-gather -- every rank assembles and transforms all Nperp lines of length
|
||||
// G, keeping only its own L points. The transpose partitions the Nperp
|
||||
// perpendicular lines across the P ranks along dim, so each rank transforms
|
||||
// only ceil(Nperp/P) lines and moves (P-1)/P of its data instead of P-1
|
||||
// redundant copies. See CartesianRingAllToAll in communicator/RingAllReduce.h.
|
||||
const int Ndim = grid->Nd();
|
||||
int L = grid->_ldimensions[dim];
|
||||
int G = grid->_fdimensions[dim];
|
||||
int Ncomp = sizeof(sobj) / sizeof(scalar);
|
||||
int64_t Nlow = 1, Nhigh = 1;
|
||||
for (int d = 0; d < dim; d++) Nlow *= grid->_ldimensions[d];
|
||||
for (int d = dim+1; d < Ndim; d++) Nhigh *= grid->_ldimensions[d];
|
||||
int64_t Nperp = Nlow * Nhigh;
|
||||
|
||||
deviceVector<scalar> pgbuf(Nperp * Ncomp * G);
|
||||
scalar *pgbuf_v = &pgbuf[0];
|
||||
int howmany = Ncomp * Nperp;
|
||||
|
||||
scalar div;
|
||||
if (sign == FFTW_BACKWARD) div = 1.0 / G;
|
||||
else if (sign == FFTW_FORWARD) div = 1.0;
|
||||
else GRID_ASSERT(0);
|
||||
|
||||
double t_pencil = 0, t_fft = 0, t_copy = 0, t_shift = 0;
|
||||
double t_total = -usecond();
|
||||
|
||||
result = source;
|
||||
int pc = grid->_processor_coor[dim];
|
||||
|
||||
const Coordinate ldims = grid->_ldimensions;
|
||||
const Coordinate rdims = grid->_rdimensions;
|
||||
const Coordinate sdims = grid->_simd_layout;
|
||||
const Coordinate processors = grid->_processors;
|
||||
|
||||
Coordinate pgdims(Ndim);
|
||||
pgdims[0] = G;
|
||||
for (int d = 0, dd = 1; d < Ndim; d++)
|
||||
if (d != dim) pgdims[dd++] = ldims[d];
|
||||
int64_t pgvol = 1;
|
||||
for (int d = 0; d < Ndim; d++) pgvol *= pgdims[d];
|
||||
|
||||
const int Nsimd = vobj::Nsimd();
|
||||
t_pencil = -usecond();
|
||||
for (int p_idx = 0; p_idx < processors[dim]; p_idx++) {
|
||||
t_copy -= usecond();
|
||||
autoView(r_v, result, AcceleratorRead);
|
||||
accelerator_for(idx, grid->oSites(), vobj::Nsimd(), {
|
||||
#ifdef GRID_SIMT
|
||||
{
|
||||
FFTW_scalar *in = (FFTW_scalar *)&pgbuf_v[0];
|
||||
FFTW_scalar *out= (FFTW_scalar *)&pgbuf_v[0];
|
||||
p = FFTW<scalar>::fftw_plan_many_dft(rank,n,howmany,
|
||||
in,inembed,
|
||||
istride,idist,
|
||||
out,onembed,
|
||||
ostride, odist,
|
||||
sign,FFTW_ESTIMATE);
|
||||
}
|
||||
|
||||
// Barrel shift and collect global pencil
|
||||
Coordinate lcoor(Nd), gcoor(Nd);
|
||||
result = source;
|
||||
int pc = processor_coor[dim];
|
||||
for(int p=0;p<processors[dim];p++) {
|
||||
{
|
||||
autoView(r_v,result,CpuRead);
|
||||
autoView(p_v,pgbuf,CpuWrite);
|
||||
thread_for(idx, sgrid->lSites(),{
|
||||
Coordinate cbuf(Nd);
|
||||
sobj s;
|
||||
sgrid->LocalIndexToLocalCoor(idx,cbuf);
|
||||
peekLocalSite(s,r_v,cbuf);
|
||||
cbuf[dim]+=((pc+p) % processors[dim])*L;
|
||||
pokeLocalSite(s,p_v,cbuf);
|
||||
});
|
||||
}
|
||||
if (p != processors[dim] - 1) {
|
||||
result = Cshift(result,dim,L);
|
||||
int lane = acceleratorSIMTlane(Nsimd);
|
||||
#else
|
||||
for (int lane = 0; lane < Nsimd; lane++) {
|
||||
#endif
|
||||
Coordinate icoor, ocoor, pgcoor;
|
||||
Lexicographic::CoorFromIndex(icoor, lane, sdims);
|
||||
Lexicographic::CoorFromIndex(ocoor, idx, rdims);
|
||||
pgcoor[0] = ocoor[dim] + icoor[dim]*rdims[dim] + ((pc+p_idx)%processors[dim])*L;
|
||||
for (int d = 0, dd = 1; d < Ndim; d++)
|
||||
if (d != dim) { pgcoor[dd] = ocoor[d] + icoor[d]*rdims[d]; dd++; }
|
||||
int64_t pgidx;
|
||||
Lexicographic::IndexFromCoor(pgcoor, pgidx, pgdims);
|
||||
vector_type *from = (vector_type *)&r_v[idx];
|
||||
scalar_type stmp;
|
||||
for (int w = 0; w < Ncomp; w++) {
|
||||
stmp = getlane(from[w], lane);
|
||||
pgbuf_v[pgidx + w*pgvol] = stmp;
|
||||
}
|
||||
#ifdef GRID_SIMT
|
||||
}
|
||||
|
||||
// Loop over orthog coords
|
||||
int NN=pencil_g.lSites();
|
||||
GridStopWatch timer;
|
||||
timer.Start();
|
||||
thread_for( idx,NN,{
|
||||
Coordinate cbuf(Nd);
|
||||
pencil_g.LocalIndexToLocalCoor(idx, cbuf);
|
||||
if ( cbuf[dim] == 0 ) { // restricts loop to plane at lcoor[dim]==0
|
||||
FFTW_scalar *in = (FFTW_scalar *)&pgbuf_v[idx];
|
||||
FFTW_scalar *out= (FFTW_scalar *)&pgbuf_v[idx];
|
||||
FFTW<scalar>::fftw_execute_dft(p,in,out);
|
||||
}
|
||||
#else
|
||||
}
|
||||
#endif
|
||||
});
|
||||
timer.Stop();
|
||||
|
||||
// performance counting
|
||||
double add,mul,fma;
|
||||
FFTW<scalar>::fftw_flops(p,&add,&mul,&fma);
|
||||
flops_call = add+mul+2.0*fma;
|
||||
usec += timer.useconds();
|
||||
flops+= flops_call*NN;
|
||||
|
||||
// writing out result
|
||||
t_copy += usecond();
|
||||
if (p_idx != processors[dim] - 1) {
|
||||
Lattice<vobj> temp(grid);
|
||||
t_shift -= usecond();
|
||||
temp = Cshift(result, dim, L); result = temp;
|
||||
t_shift += usecond();
|
||||
}
|
||||
}
|
||||
t_pencil += usecond();
|
||||
|
||||
FFTW_scalar *in = (FFTW_scalar *)pgbuf_v;
|
||||
FFTW_scalar *out = (FFTW_scalar *)pgbuf_v;
|
||||
t_fft = -usecond();
|
||||
FFTW<scalar>::fftw_execute_dft(p, in, out, sign);
|
||||
t_fft += usecond();
|
||||
|
||||
flops_call = 5.0 * howmany * G * log2(G);
|
||||
usec = t_fft;
|
||||
flops = flops_call;
|
||||
|
||||
result = Zero();
|
||||
double t_insert = -usecond();
|
||||
{
|
||||
autoView(r_v, result, AcceleratorWrite);
|
||||
accelerator_for(idx, grid->oSites(), Nsimd, {
|
||||
#ifdef GRID_SIMT
|
||||
{
|
||||
autoView(pgbuf_v,pgbuf,CpuRead);
|
||||
autoView(result_v,result,CpuWrite);
|
||||
thread_for(idx,sgrid->lSites(),{
|
||||
Coordinate clbuf(Nd), cgbuf(Nd);
|
||||
sobj s;
|
||||
sgrid->LocalIndexToLocalCoor(idx,clbuf);
|
||||
cgbuf = clbuf;
|
||||
cgbuf[dim] = clbuf[dim]+L*pc;
|
||||
peekLocalSite(s,pgbuf_v,cgbuf);
|
||||
pokeLocalSite(s,result_v,clbuf);
|
||||
int lane = acceleratorSIMTlane(Nsimd);
|
||||
#else
|
||||
for (int lane = 0; lane < Nsimd; lane++) {
|
||||
#endif
|
||||
Coordinate icoor(Ndim), ocoor(Ndim), pgcoor(Ndim);
|
||||
Lexicographic::CoorFromIndex(icoor, lane, sdims);
|
||||
Lexicographic::CoorFromIndex(ocoor, idx, rdims);
|
||||
pgcoor[0] = ocoor[dim] + icoor[dim]*rdims[dim] + pc*L;
|
||||
for (int d = 0, dd = 1; d < Ndim; d++)
|
||||
if (d != dim) { pgcoor[dd] = ocoor[d] + icoor[d]*rdims[d]; dd++; }
|
||||
int64_t pgidx;
|
||||
Lexicographic::IndexFromCoor(pgcoor, pgidx, pgdims);
|
||||
vector_type *to = (vector_type *)&r_v[idx];
|
||||
scalar_type stmp;
|
||||
for (int w = 0; w < Ncomp; w++) {
|
||||
stmp = pgbuf_v[pgidx + w*pgvol];
|
||||
putlane(to[w], stmp, lane);
|
||||
}
|
||||
#ifdef GRID_SIMT
|
||||
}
|
||||
#else
|
||||
}
|
||||
#endif
|
||||
});
|
||||
}
|
||||
result = result * div;
|
||||
t_insert += usecond();
|
||||
t_total += usecond();
|
||||
|
||||
std::cout << GridLogPerformance << " FFT took " << t_total/1.0e6 << " s" << std::endl;
|
||||
std::cout << GridLogPerformance << " FFT pencil " << t_pencil/1.0e6 << " s" << std::endl;
|
||||
std::cout << GridLogPerformance << " of which copy " << t_copy/1.0e6 << " s" << std::endl;
|
||||
std::cout << GridLogPerformance << " of which shift" << t_shift/1.0e6 << " s" << std::endl;
|
||||
std::cout << GridLogPerformance << " FFT kernels " << t_fft/1.0e6 << " s" << std::endl;
|
||||
std::cout << GridLogPerformance << " FFT insert " << t_insert/1.0e6 << " s" << std::endl;
|
||||
#endif
|
||||
|
||||
// ==================== transpose / all-to-all pencil FFT ====================
|
||||
//
|
||||
// P ranks lie along dim; each holds a local L-slab (L=ldim) of every global
|
||||
// line (length G=fdim=L*P) and Nperp perpendicular lines. We partition the
|
||||
// Nperp lines across the P ranks: the rank at coord `own` owns the lines with
|
||||
// olin/Oloc == own, gathers their full G points via a cartesian all-to-all,
|
||||
// transforms only Oloc = ceil(Nperp/P) of them, then scatters the result back
|
||||
// with the inverse all-to-all.
|
||||
//
|
||||
// The owned-line count is CEIL-padded to a multiple of P (Oloc*P >= Nperp) so
|
||||
// the all-to-all stays SYMMETRIC (one uniform chunk) for ANY (P,Nperp) -- in
|
||||
// particular a P carrying a factor absent from Nperp, e.g. P=3 on the T axis
|
||||
// of 128^3x288 whose perpendicular volume is a pure power of two. This keeps
|
||||
// the transpose as total over decompositions as the barrel it replaces (no
|
||||
// new geometry constraint), at a cost of <= (P-1) padded lines out of Nperp.
|
||||
// Padding slots olin in [Nperp, Oloc*P) are never packed and never unpacked.
|
||||
//
|
||||
// The load-bearing identity: the all-to-all block index == cartesian coord
|
||||
// along dim == which L-slab [c*L, c*L+L) of the global line -- so the block a
|
||||
// rank receives carries the global-x tag needed to order the FFT input.
|
||||
//
|
||||
// Degenerate P: P=1 -> both all-to-alls are self-copies and the reorders are
|
||||
// the identity (G=L), i.e. a pure local FFT. P=2 -> each all-to-all moves
|
||||
// half a field, two of them one field, equal to the barrel's single Cshift.
|
||||
{
|
||||
const int Ndim = grid->Nd();
|
||||
int L = grid->_ldimensions[dim];
|
||||
int G = grid->_fdimensions[dim];
|
||||
int Ncomp = sizeof(sobj) / sizeof(scalar);
|
||||
int P = grid->_processors[dim];
|
||||
int64_t Nperp = 1;
|
||||
for (int d = 0; d < Ndim; d++)
|
||||
if (d != dim) Nperp *= grid->_ldimensions[d];
|
||||
|
||||
int64_t Oloc = (Nperp + P - 1) / P; // ceil: owned (padded) lines/rank
|
||||
int64_t chunk = (int64_t)L * Oloc * Ncomp; // one all-to-all block (uniform)
|
||||
int64_t nbuf = (int64_t)P * chunk; // == Ncomp*Oloc*G, one field's worth
|
||||
int64_t howmany_local = (int64_t)Ncomp * Oloc;
|
||||
|
||||
scalar div;
|
||||
if (sign == FFTW_BACKWARD) div = 1.0 / G;
|
||||
else if (sign == FFTW_FORWARD) div = 1.0;
|
||||
else GRID_ASSERT(0);
|
||||
|
||||
double t_total = -usecond();
|
||||
double t_pack = 0, t_a2a = 0, t_reorder = 0, t_fft = 0, t_unpack = 0;
|
||||
|
||||
deviceVector<scalar> sbuf(nbuf);
|
||||
deviceVector<scalar> rbuf(nbuf);
|
||||
deviceVector<scalar> pgbuf(nbuf); // FFTW pencil buffer, Ncomp*Oloc lines of G
|
||||
scalar *sbuf_v = &sbuf[0];
|
||||
scalar *rbuf_v = &rbuf[0];
|
||||
scalar *pgbuf_v = &pgbuf[0];
|
||||
|
||||
// Pad slots (olin in [Nperp, Oloc*P)) are never packed, so they would carry
|
||||
// garbage device memory into the FFT. Zero them so the padded lines stay
|
||||
// finite -- but ONLY when padding is actually present. In the common
|
||||
// Nperp % P == 0 case pack writes every sbuf entry bijectively, so skip the
|
||||
// whole-buffer memset and its device sync entirely.
|
||||
if ( (int64_t)Oloc*P != Nperp )
|
||||
acceleratorMemSet(sbuf_v, 0, nbuf*sizeof(scalar));
|
||||
|
||||
const Coordinate ldims = grid->_ldimensions;
|
||||
const Coordinate rdims = grid->_rdimensions;
|
||||
const Coordinate sdims = grid->_simd_layout;
|
||||
const int Nsimd = vobj::Nsimd();
|
||||
|
||||
// ---- 1. pack: source -> sbuf. block = owner coord; payload xloc + L*(slot + Oloc*w)
|
||||
t_pack -= usecond();
|
||||
{
|
||||
autoView(s_v, source, AcceleratorRead);
|
||||
accelerator_for(idx, grid->oSites(), Nsimd, {
|
||||
#ifdef GRID_SIMT
|
||||
{
|
||||
int lane = acceleratorSIMTlane(Nsimd);
|
||||
#else
|
||||
for (int lane = 0; lane < Nsimd; lane++) {
|
||||
#endif
|
||||
Coordinate icoor(Ndim), ocoor(Ndim);
|
||||
Lexicographic::CoorFromIndex(icoor, lane, sdims);
|
||||
Lexicographic::CoorFromIndex(ocoor, idx, rdims);
|
||||
int64_t xloc = ocoor[dim] + icoor[dim]*rdims[dim];
|
||||
int64_t olin = 0, str = 1;
|
||||
for (int d = 0; d < Ndim; d++) {
|
||||
if (d == dim) continue;
|
||||
int64_t c = ocoor[d] + icoor[d]*rdims[d];
|
||||
olin += str * c;
|
||||
str *= ldims[d];
|
||||
}
|
||||
int64_t own = olin / Oloc;
|
||||
int64_t slot = olin - own*Oloc;
|
||||
vector_type *from = (vector_type *)&s_v[idx];
|
||||
for (int w = 0; w < Ncomp; w++) {
|
||||
scalar_type stmp = getlane(from[w], lane);
|
||||
sbuf_v[ own*chunk + xloc + L*(slot + Oloc*w) ] = stmp;
|
||||
}
|
||||
#ifdef GRID_SIMT
|
||||
}
|
||||
#else
|
||||
}
|
||||
#endif
|
||||
});
|
||||
}
|
||||
result = result*div;
|
||||
|
||||
// destroying plan
|
||||
FFTW<scalar>::fftw_destroy_plan(p);
|
||||
t_pack += usecond();
|
||||
|
||||
// ---- 2. forward all-to-all: gather my owned lines' L-slabs from every rank
|
||||
t_a2a -= usecond();
|
||||
CartesianRingAllToAll(grid, sbuf_v, rbuf_v, (uint64_t)chunk, dim);
|
||||
t_a2a += usecond();
|
||||
|
||||
// ---- 3. reorder rbuf -> pgbuf: contiguous G-lines (w,slot), xpos = src*L + xloc
|
||||
t_reorder -= usecond();
|
||||
accelerator_for(q, nbuf, 1, {
|
||||
int64_t xpos = q % G;
|
||||
int64_t t = q / G; // = w*Oloc + slot
|
||||
int64_t slot = t % Oloc;
|
||||
int64_t w = t / Oloc;
|
||||
int64_t src = xpos / L;
|
||||
int64_t xloc = xpos % L;
|
||||
pgbuf_v[q] = rbuf_v[ src*chunk + xloc + L*(slot + Oloc*w) ];
|
||||
});
|
||||
t_reorder += usecond();
|
||||
|
||||
// ---- 4. FFT: Ncomp*Oloc contiguous lines of length G (istride 1, idist G)
|
||||
{
|
||||
FFTW_scalar *in = (FFTW_scalar *)pgbuf_v;
|
||||
FFTW_scalar *out = (FFTW_scalar *)pgbuf_v;
|
||||
t_fft -= usecond();
|
||||
FFTW<scalar>::fftw_execute_dft(p, in, out, sign);
|
||||
t_fft += usecond();
|
||||
}
|
||||
flops_call = 5.0 * (double)howmany_local * G * log2(G);
|
||||
usec = (uint64_t)t_fft;
|
||||
flops = flops_call;
|
||||
|
||||
// ---- 5a. reorder pgbuf -> sbuf: block = destination coord; xpos = dst*L + xloc
|
||||
t_reorder -= usecond();
|
||||
accelerator_for(j, nbuf, 1, {
|
||||
int64_t dst = j / chunk;
|
||||
int64_t r = j % chunk;
|
||||
int64_t xloc = r % L;
|
||||
int64_t u = r / L; // = slot + Oloc*w
|
||||
int64_t slot = u % Oloc;
|
||||
int64_t w = u / Oloc;
|
||||
int64_t xpos = dst*L + xloc;
|
||||
sbuf_v[j] = pgbuf_v[ w*Oloc*G + slot*G + xpos ];
|
||||
});
|
||||
t_reorder += usecond();
|
||||
|
||||
// ---- 5b. inverse all-to-all: scatter transformed L-slabs back
|
||||
t_a2a -= usecond();
|
||||
CartesianRingAllToAll(grid, sbuf_v, rbuf_v, (uint64_t)chunk, dim);
|
||||
t_a2a += usecond();
|
||||
|
||||
// ---- 5c. unpack rbuf -> result (x div); block = owner coord of each line
|
||||
t_unpack -= usecond();
|
||||
{
|
||||
autoView(r_v, result, AcceleratorWrite);
|
||||
accelerator_for(idx, grid->oSites(), Nsimd, {
|
||||
#ifdef GRID_SIMT
|
||||
{
|
||||
int lane = acceleratorSIMTlane(Nsimd);
|
||||
#else
|
||||
for (int lane = 0; lane < Nsimd; lane++) {
|
||||
#endif
|
||||
Coordinate icoor(Ndim), ocoor(Ndim);
|
||||
Lexicographic::CoorFromIndex(icoor, lane, sdims);
|
||||
Lexicographic::CoorFromIndex(ocoor, idx, rdims);
|
||||
int64_t xloc = ocoor[dim] + icoor[dim]*rdims[dim];
|
||||
int64_t olin = 0, str = 1;
|
||||
for (int d = 0; d < Ndim; d++) {
|
||||
if (d == dim) continue;
|
||||
int64_t c = ocoor[d] + icoor[d]*rdims[d];
|
||||
olin += str * c;
|
||||
str *= ldims[d];
|
||||
}
|
||||
int64_t own = olin / Oloc;
|
||||
int64_t slot = olin - own*Oloc;
|
||||
vector_type *to = (vector_type *)&r_v[idx];
|
||||
for (int w = 0; w < Ncomp; w++) {
|
||||
scalar_type stmp = div * rbuf_v[ own*chunk + xloc + L*(slot + Oloc*w) ];
|
||||
putlane(to[w], stmp, lane);
|
||||
}
|
||||
#ifdef GRID_SIMT
|
||||
}
|
||||
#else
|
||||
}
|
||||
#endif
|
||||
});
|
||||
}
|
||||
t_unpack += usecond();
|
||||
t_total += usecond();
|
||||
|
||||
std::cout << GridLogPerformance << " FFT took " << t_total/1.0e6 << " s (transpose P=" << P << ")" << std::endl;
|
||||
std::cout << GridLogPerformance << " FFT pack " << t_pack/1.0e6 << " s" << std::endl;
|
||||
std::cout << GridLogPerformance << " FFT alltoall " << t_a2a/1.0e6 << " s" << std::endl;
|
||||
std::cout << GridLogPerformance << " FFT reorder " << t_reorder/1.0e6 << " s" << std::endl;
|
||||
std::cout << GridLogPerformance << " FFT kernels " << t_fft/1.0e6 << " s" << std::endl;
|
||||
std::cout << GridLogPerformance << " FFT unpack " << t_unpack/1.0e6 << " s" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
class FFT : public FFTbase {
|
||||
public:
|
||||
FFT(GridCartesian *grid) : FFTbase(grid) {}
|
||||
~FFT() {}
|
||||
|
||||
template<class vobj>
|
||||
void FFT_dim_mask(Lattice<vobj> &result, const Lattice<vobj> &source, Coordinate mask, int sign) {
|
||||
const int Ndim = _grid->Nd();
|
||||
Lattice<vobj> tmp = source;
|
||||
for (int d = 0; d < Ndim; d++) {
|
||||
if (mask[d]) {
|
||||
FFT_dim(result, tmp, d, sign);
|
||||
tmp = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class vobj>
|
||||
void FFT_all_dim(Lattice<vobj> &result, const Lattice<vobj> &source, int sign) {
|
||||
Coordinate mask(_grid->Nd(), 1);
|
||||
FFT_dim_mask(result, source, mask, sign);
|
||||
}
|
||||
|
||||
template<class vobj>
|
||||
void FFT_dim(Lattice<vobj> &result, const Lattice<vobj> &source, int dim, int sign) {
|
||||
GRID_ASSERT(source.Grid() == _grid);
|
||||
GRID_ASSERT(result.Grid() == _grid);
|
||||
conformable(result.Grid(), source.Grid());
|
||||
|
||||
typedef typename vobj::scalar_type scalar;
|
||||
typedef typename vobj::scalar_object sobj;
|
||||
typedef typename FFTW<scalar>::FFTW_scalar FFTW_scalar;
|
||||
typedef typename FFTW<scalar>::FFTW_plan FFTW_plan;
|
||||
|
||||
const int Ndim = _grid->Nd();
|
||||
int G = _grid->_fdimensions[dim];
|
||||
int Ncomp = sizeof(sobj) / sizeof(scalar);
|
||||
int64_t Nperp = 1;
|
||||
for (int d = 0; d < Ndim; d++)
|
||||
if (d != dim) Nperp *= _grid->_ldimensions[d];
|
||||
int P = _grid->_processors[dim];
|
||||
int64_t Oloc = (Nperp + P - 1) / P; // ceil-padded owned lines/rank (see FFT_dim_execute)
|
||||
int n[] = {G};
|
||||
int howmany = Ncomp * (int)Oloc;
|
||||
|
||||
deviceVector<scalar> dummy(2);
|
||||
FFTW_scalar *buf = (FFTW_scalar *)&dummy[0];
|
||||
FFTW_plan p = FFTW<scalar>::fftw_plan_many_dft(1, n, howmany,
|
||||
buf, n, 1, G,
|
||||
buf, n, 1, G,
|
||||
sign, FFTW_ESTIMATE);
|
||||
FFT_dim_execute(result, source, dim, sign, p, _grid, flops, flops_call, usec);
|
||||
FFTW<scalar>::fftw_destroy_plan(p);
|
||||
}
|
||||
};
|
||||
|
||||
template<class vobj>
|
||||
class PlannedFFT : public FFTbase {
|
||||
private:
|
||||
typedef typename vobj::scalar_type scalar;
|
||||
typedef typename vobj::scalar_object sobj;
|
||||
typedef typename vobj::vector_type vector_type;
|
||||
typedef typename FFTW<scalar>::FFTW_scalar FFTW_scalar;
|
||||
typedef typename FFTW<scalar>::FFTW_plan FFTW_plan;
|
||||
|
||||
std::vector<FFTW_plan> forward_plans;
|
||||
std::vector<FFTW_plan> backward_plans;
|
||||
|
||||
void PlanCreate() {
|
||||
const int Ndim = _grid->Nd();
|
||||
forward_plans.resize(Ndim);
|
||||
backward_plans.resize(Ndim);
|
||||
|
||||
for (int d = 0; d < Ndim; d++) {
|
||||
int G = _grid->_fdimensions[d];
|
||||
int Ncomp = sizeof(sobj) / sizeof(scalar);
|
||||
int64_t Nperp = 1;
|
||||
for (int dd = 0; dd < Ndim; dd++)
|
||||
if (dd != d) Nperp *= _grid->_ldimensions[dd];
|
||||
int P = _grid->_processors[d];
|
||||
int64_t Oloc = (Nperp + P - 1) / P; // ceil-padded owned lines/rank (see FFT_dim_execute)
|
||||
int howmany = Ncomp * (int)Oloc;
|
||||
int n[] = {G};
|
||||
|
||||
deviceVector<scalar> dummy(2);
|
||||
FFTW_scalar *buf = (FFTW_scalar *)&dummy[0];
|
||||
|
||||
forward_plans[d] = FFTW<scalar>::fftw_plan_many_dft(1, n, howmany, buf, n, 1, G, buf, n, 1, G, FFTW_FORWARD, FFTW_ESTIMATE);
|
||||
backward_plans[d] = FFTW<scalar>::fftw_plan_many_dft(1, n, howmany, buf, n, 1, G, buf, n, 1, G, FFTW_BACKWARD, FFTW_ESTIMATE);
|
||||
}
|
||||
}
|
||||
|
||||
void PlanDestroy() {
|
||||
for (auto p : forward_plans) FFTW<scalar>::fftw_destroy_plan(p);
|
||||
for (auto p : backward_plans) FFTW<scalar>::fftw_destroy_plan(p);
|
||||
forward_plans.clear();
|
||||
backward_plans.clear();
|
||||
}
|
||||
|
||||
public:
|
||||
PlannedFFT(GridCartesian *grid) : FFTbase(grid) { PlanCreate(); }
|
||||
~PlannedFFT() { PlanDestroy(); }
|
||||
|
||||
void FFT_dim_mask(Lattice<vobj> &result, const Lattice<vobj> &source, Coordinate mask, int sign) {
|
||||
const int Ndim = _grid->Nd();
|
||||
Lattice<vobj> tmp = source;
|
||||
for (int d = 0; d < Ndim; d++) {
|
||||
if (mask[d]) {
|
||||
FFT_dim(result, tmp, d, sign);
|
||||
tmp = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FFT_all_dim(Lattice<vobj> &result, const Lattice<vobj> &source, int sign) {
|
||||
Coordinate mask(_grid->Nd(), 1);
|
||||
FFT_dim_mask(result, source, mask, sign);
|
||||
}
|
||||
|
||||
void FFT_dim(Lattice<vobj> &result, const Lattice<vobj> &source, int dim, int sign) {
|
||||
GRID_ASSERT(source.Grid() == _grid);
|
||||
GRID_ASSERT(result.Grid() == _grid);
|
||||
GRID_ASSERT((int)forward_plans.size() == _grid->Nd());
|
||||
conformable(result.Grid(), source.Grid());
|
||||
FFTW_plan p = (sign == forward ? forward_plans : backward_plans)[dim];
|
||||
FFT_dim_execute(result, source, dim, sign, p, _grid, flops, flops_call, usec);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -52,9 +52,31 @@ public:
|
||||
virtual void AdjOp (const Field &in, Field &out) = 0; // Abstract base
|
||||
virtual void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2)=0;
|
||||
virtual void HermOp(const Field &in, Field &out)=0;
|
||||
|
||||
// As CheckerBoardedSparseMatrixBase::SplitClone; the returned bundle's Linop is the
|
||||
// same kind of wrapper as this one. Collective. nullptr if not supported.
|
||||
virtual SplitOperator<Field> *SplitClone(const Coordinate &mpi_split)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual ~LinearOperatorBase(){};
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// The wrappers below are templates on an arbitrary Matrix, and their virtual SplitClone is
|
||||
// instantiated with the class, so it must compile for Matrix types with no SplitClone.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
template<class Field,class Matrix>
|
||||
SplitOperator<Field> *SplitCloneMatrix(Matrix &Mat,const Coordinate &mpi_split)
|
||||
{
|
||||
if constexpr ( std::is_base_of<CheckerBoardedSparseMatrixBase<Field>,Matrix>::value ) {
|
||||
return Mat.SplitClone(mpi_split);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// By sharing the class for Sparse Matrix across multiple operator wrappers, we can share code
|
||||
@@ -64,7 +86,7 @@ public:
|
||||
//
|
||||
// I'm not entirely happy with implementation; to share the Schur code between herm and non-herm
|
||||
// while still having a "OpAndNorm" in the abstract base I had to implement it in both cases
|
||||
// with an assert trap in the non-herm. This isn't right; there must be a better C++ way to
|
||||
// with an GRID_ASSERT trap in the non-herm. This isn't right; there must be a better C++ way to
|
||||
// do it, but I fear it required multiple inheritance and mixed in abstract base classes
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -102,6 +124,49 @@ public:
|
||||
void HermOp(const Field &in, Field &out){
|
||||
_Mat.MdagM(in,out);
|
||||
}
|
||||
|
||||
virtual SplitOperator<Field> *SplitClone(const Coordinate &mpi_split)
|
||||
{
|
||||
SplitOperator<Field> *split = SplitCloneMatrix<Field>(_Mat,mpi_split);
|
||||
if ( split == nullptr ) {
|
||||
return nullptr;
|
||||
}
|
||||
split->Linop = new MdagMLinearOperator<CheckerBoardedSparseMatrixBase<Field>,Field>(*split->Matrix);
|
||||
split->FieldGrid = split->Matrix->Grid();
|
||||
return split;
|
||||
}
|
||||
};
|
||||
template<class Matrix,class Field>
|
||||
class MMdagLinearOperator : public LinearOperatorBase<Field> {
|
||||
Matrix &_Mat;
|
||||
public:
|
||||
MMdagLinearOperator(Matrix &Mat): _Mat(Mat){};
|
||||
|
||||
// Support for coarsening to a multigrid
|
||||
void OpDiag (const Field &in, Field &out) {
|
||||
_Mat.Mdiag(in,out);
|
||||
}
|
||||
void OpDir (const Field &in, Field &out,int dir,int disp) {
|
||||
_Mat.Mdir(in,out,dir,disp);
|
||||
}
|
||||
void OpDirAll (const Field &in, std::vector<Field> &out){
|
||||
_Mat.MdirAll(in,out);
|
||||
};
|
||||
void Op (const Field &in, Field &out){
|
||||
_Mat.M(in,out);
|
||||
}
|
||||
void AdjOp (const Field &in, Field &out){
|
||||
_Mat.Mdag(in,out);
|
||||
}
|
||||
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){
|
||||
_Mat.MMdag(in,out);
|
||||
ComplexD dot = innerProduct(in,out);
|
||||
n1=real(dot);
|
||||
n2=norm2(out);
|
||||
}
|
||||
void HermOp(const Field &in, Field &out){
|
||||
_Mat.MMdag(in,out);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
@@ -116,22 +181,22 @@ public:
|
||||
// Support for coarsening to a multigrid
|
||||
void OpDiag (const Field &in, Field &out) {
|
||||
_Mat.Mdiag(in,out);
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void OpDir (const Field &in, Field &out,int dir,int disp) {
|
||||
_Mat.Mdir(in,out,dir,disp);
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void OpDirAll (const Field &in, std::vector<Field> &out){
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
};
|
||||
void Op (const Field &in, Field &out){
|
||||
_Mat.M(in,out);
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void AdjOp (const Field &in, Field &out){
|
||||
_Mat.Mdag(in,out);
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){
|
||||
HermOp(in,out);
|
||||
@@ -145,6 +210,67 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Create a shifted HermOp
|
||||
////////////////////////////////////////////////////////////////////
|
||||
template<class Field>
|
||||
class ShiftedHermOpLinearOperator : public LinearOperatorBase<Field> {
|
||||
LinearOperatorBase<Field> &_Mat;
|
||||
RealD _shift;
|
||||
public:
|
||||
ShiftedHermOpLinearOperator(LinearOperatorBase<Field> &Mat,RealD shift): _Mat(Mat), _shift(shift){};
|
||||
// Support for coarsening to a multigrid
|
||||
void OpDiag (const Field &in, Field &out) {
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void OpDir (const Field &in, Field &out,int dir,int disp) {
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void OpDirAll (const Field &in, std::vector<Field> &out){
|
||||
GRID_ASSERT(0);
|
||||
};
|
||||
void Op (const Field &in, Field &out){
|
||||
HermOp(in,out);
|
||||
}
|
||||
void AdjOp (const Field &in, Field &out){
|
||||
HermOp(in,out);
|
||||
}
|
||||
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){
|
||||
HermOp(in,out);
|
||||
ComplexD dot = innerProduct(in,out);
|
||||
n1=real(dot);
|
||||
n2=norm2(out);
|
||||
}
|
||||
void HermOp(const Field &in, Field &out){
|
||||
_Mat.HermOp(in,out);
|
||||
out = out + _shift*in;
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Present any LinearOperatorBase as Hermitian: Op = AdjOp = HermOp.
|
||||
// For coarsening (CoarsenOperator applies Op) an HPD operator whose
|
||||
// Op is a factor and HermOp the product, e.g. SchurDiagMooee or MdagM.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
template<class Field>
|
||||
class HermOpAdaptor : public LinearOperatorBase<Field> {
|
||||
LinearOperatorBase<Field> &_Mat;
|
||||
public:
|
||||
HermOpAdaptor(LinearOperatorBase<Field> &Mat): _Mat(Mat){};
|
||||
void OpDiag (const Field &in, Field &out) { GRID_ASSERT(0); }
|
||||
void OpDir (const Field &in, Field &out,int dir,int disp) { GRID_ASSERT(0); }
|
||||
void OpDirAll(const Field &in, std::vector<Field> &out) { GRID_ASSERT(0); }
|
||||
void Op (const Field &in, Field &out){ _Mat.HermOp(in,out); }
|
||||
void AdjOp (const Field &in, Field &out){ _Mat.HermOp(in,out); }
|
||||
void HermOp (const Field &in, Field &out){ _Mat.HermOp(in,out); }
|
||||
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){
|
||||
HermOp(in,out);
|
||||
ComplexD dot = innerProduct(in,out);
|
||||
n1=real(dot);
|
||||
n2=norm2(out);
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Wrap an already herm matrix
|
||||
////////////////////////////////////////////////////////////////////
|
||||
@@ -201,10 +327,42 @@ public:
|
||||
_Mat.Mdag(in,out);
|
||||
}
|
||||
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void HermOp(const Field &in, Field &out){
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
};
|
||||
template<class Matrix,class Field>
|
||||
class ShiftedNonHermitianLinearOperator : public LinearOperatorBase<Field> {
|
||||
Matrix &_Mat;
|
||||
RealD shift;
|
||||
public:
|
||||
ShiftedNonHermitianLinearOperator(Matrix &Mat,RealD shft): _Mat(Mat),shift(shft){};
|
||||
// Support for coarsening to a multigrid
|
||||
void OpDiag (const Field &in, Field &out) {
|
||||
_Mat.Mdiag(in,out);
|
||||
out = out + shift*in;
|
||||
}
|
||||
void OpDir (const Field &in, Field &out,int dir,int disp) {
|
||||
_Mat.Mdir(in,out,dir,disp);
|
||||
}
|
||||
void OpDirAll (const Field &in, std::vector<Field> &out){
|
||||
_Mat.MdirAll(in,out);
|
||||
};
|
||||
void Op (const Field &in, Field &out){
|
||||
_Mat.M(in,out);
|
||||
out = out + shift * in;
|
||||
}
|
||||
void AdjOp (const Field &in, Field &out){
|
||||
_Mat.Mdag(in,out);
|
||||
out = out + shift * in;
|
||||
}
|
||||
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void HermOp(const Field &in, Field &out){
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -243,13 +401,13 @@ class SchurOperatorBase : public LinearOperatorBase<Field> {
|
||||
}
|
||||
// Support for coarsening to a multigrid
|
||||
void OpDiag (const Field &in, Field &out) {
|
||||
assert(0); // must coarsen the unpreconditioned system
|
||||
GRID_ASSERT(0); // must coarsen the unpreconditioned system
|
||||
}
|
||||
void OpDir (const Field &in, Field &out,int dir,int disp) {
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void OpDirAll (const Field &in, std::vector<Field> &out){
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
};
|
||||
};
|
||||
template<class Matrix,class Field>
|
||||
@@ -276,6 +434,17 @@ template<class Matrix,class Field>
|
||||
_Mat.MooeeDag(in,out);
|
||||
axpy(out,-1.0,tmp,out);
|
||||
}
|
||||
|
||||
virtual SplitOperator<Field> *SplitClone(const Coordinate &mpi_split)
|
||||
{
|
||||
SplitOperator<Field> *split = SplitCloneMatrix<Field>(_Mat,mpi_split);
|
||||
if ( split == nullptr ) {
|
||||
return nullptr;
|
||||
}
|
||||
split->Linop = new SchurDiagMooeeOperator<CheckerBoardedSparseMatrixBase<Field>,Field>(*split->Matrix);
|
||||
split->FieldGrid = split->Matrix->RedBlackGrid();
|
||||
return split;
|
||||
}
|
||||
};
|
||||
template<class Matrix,class Field>
|
||||
class SchurDiagOneOperator : public SchurOperatorBase<Field> {
|
||||
@@ -302,6 +471,17 @@ template<class Matrix,class Field>
|
||||
_Mat.MeooeDag(out,tmp);
|
||||
axpy(out,-1.0,tmp,in);
|
||||
}
|
||||
|
||||
virtual SplitOperator<Field> *SplitClone(const Coordinate &mpi_split)
|
||||
{
|
||||
SplitOperator<Field> *split = SplitCloneMatrix<Field>(_Mat,mpi_split);
|
||||
if ( split == nullptr ) {
|
||||
return nullptr;
|
||||
}
|
||||
split->Linop = new SchurDiagOneOperator<CheckerBoardedSparseMatrixBase<Field>,Field>(*split->Matrix);
|
||||
split->FieldGrid = split->Matrix->RedBlackGrid();
|
||||
return split;
|
||||
}
|
||||
};
|
||||
template<class Matrix,class Field>
|
||||
class SchurDiagTwoOperator : public SchurOperatorBase<Field> {
|
||||
@@ -330,6 +510,17 @@ template<class Matrix,class Field>
|
||||
|
||||
axpy(out,-1.0,tmp,in);
|
||||
}
|
||||
|
||||
virtual SplitOperator<Field> *SplitClone(const Coordinate &mpi_split)
|
||||
{
|
||||
SplitOperator<Field> *split = SplitCloneMatrix<Field>(_Mat,mpi_split);
|
||||
if ( split == nullptr ) {
|
||||
return nullptr;
|
||||
}
|
||||
split->Linop = new SchurDiagTwoOperator<CheckerBoardedSparseMatrixBase<Field>,Field>(*split->Matrix);
|
||||
split->FieldGrid = split->Matrix->RedBlackGrid();
|
||||
return split;
|
||||
}
|
||||
};
|
||||
|
||||
template<class Field>
|
||||
@@ -345,10 +536,10 @@ class NonHermitianSchurOperatorBase : public LinearOperatorBase<Field>
|
||||
MpcDag(tmp,out);
|
||||
}
|
||||
virtual void HermOpAndNorm(const Field& in, Field& out, RealD& n1, RealD& n2) {
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
virtual void HermOp(const Field& in, Field& out) {
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void Op(const Field& in, Field& out) {
|
||||
Mpc(in, out);
|
||||
@@ -358,13 +549,13 @@ class NonHermitianSchurOperatorBase : public LinearOperatorBase<Field>
|
||||
}
|
||||
// Support for coarsening to a multigrid
|
||||
void OpDiag(const Field& in, Field& out) {
|
||||
assert(0); // must coarsen the unpreconditioned system
|
||||
GRID_ASSERT(0); // must coarsen the unpreconditioned system
|
||||
}
|
||||
void OpDir(const Field& in, Field& out, int dir, int disp) {
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void OpDirAll(const Field& in, std::vector<Field>& out){
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -397,6 +588,17 @@ class NonHermitianSchurDiagMooeeOperator : public NonHermitianSchurOperatorBase
|
||||
|
||||
axpy(out, -1.0, tmp, out);
|
||||
}
|
||||
|
||||
virtual SplitOperator<Field> *SplitClone(const Coordinate &mpi_split)
|
||||
{
|
||||
SplitOperator<Field> *split = SplitCloneMatrix<Field>(_Mat,mpi_split);
|
||||
if ( split == nullptr ) {
|
||||
return nullptr;
|
||||
}
|
||||
split->Linop = new NonHermitianSchurDiagMooeeOperator<CheckerBoardedSparseMatrixBase<Field>,Field>(*split->Matrix);
|
||||
split->FieldGrid = split->Matrix->RedBlackGrid();
|
||||
return split;
|
||||
}
|
||||
};
|
||||
|
||||
template<class Matrix,class Field>
|
||||
@@ -427,6 +629,17 @@ class NonHermitianSchurDiagOneOperator : public NonHermitianSchurOperatorBase<Fi
|
||||
|
||||
axpy(out, -1.0, tmp, in);
|
||||
}
|
||||
|
||||
virtual SplitOperator<Field> *SplitClone(const Coordinate &mpi_split)
|
||||
{
|
||||
SplitOperator<Field> *split = SplitCloneMatrix<Field>(_Mat,mpi_split);
|
||||
if ( split == nullptr ) {
|
||||
return nullptr;
|
||||
}
|
||||
split->Linop = new NonHermitianSchurDiagOneOperator<CheckerBoardedSparseMatrixBase<Field>,Field>(*split->Matrix);
|
||||
split->FieldGrid = split->Matrix->RedBlackGrid();
|
||||
return split;
|
||||
}
|
||||
};
|
||||
|
||||
template<class Matrix, class Field>
|
||||
@@ -458,6 +671,17 @@ class NonHermitianSchurDiagTwoOperator : public NonHermitianSchurOperatorBase<Fi
|
||||
|
||||
axpy(out, -1.0, tmp, in);
|
||||
}
|
||||
|
||||
virtual SplitOperator<Field> *SplitClone(const Coordinate &mpi_split)
|
||||
{
|
||||
SplitOperator<Field> *split = SplitCloneMatrix<Field>(_Mat,mpi_split);
|
||||
if ( split == nullptr ) {
|
||||
return nullptr;
|
||||
}
|
||||
split->Linop = new NonHermitianSchurDiagTwoOperator<CheckerBoardedSparseMatrixBase<Field>,Field>(*split->Matrix);
|
||||
split->FieldGrid = split->Matrix->RedBlackGrid();
|
||||
return split;
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -478,7 +702,7 @@ class SchurStaggeredOperator : public SchurOperatorBase<Field> {
|
||||
public:
|
||||
SchurStaggeredOperator (Matrix &Mat): _Mat(Mat), tmp(_Mat.RedBlackGrid())
|
||||
{
|
||||
assert( _Mat.isTrivialEE() );
|
||||
GRID_ASSERT( _Mat.isTrivialEE() );
|
||||
mass = _Mat.Mass();
|
||||
}
|
||||
virtual void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2){
|
||||
@@ -509,7 +733,18 @@ class SchurStaggeredOperator : public SchurOperatorBase<Field> {
|
||||
Mpc(in,out);
|
||||
}
|
||||
virtual void MpcDagMpc(const Field &in, Field &out) {
|
||||
assert(0);// Never need with staggered
|
||||
GRID_ASSERT(0);// Never need with staggered
|
||||
}
|
||||
|
||||
virtual SplitOperator<Field> *SplitClone(const Coordinate &mpi_split)
|
||||
{
|
||||
SplitOperator<Field> *split = SplitCloneMatrix<Field>(_Mat,mpi_split);
|
||||
if ( split == nullptr ) {
|
||||
return nullptr;
|
||||
}
|
||||
split->Linop = new SchurStaggeredOperator<CheckerBoardedSparseMatrixBase<Field>,Field>(*split->Matrix);
|
||||
split->FieldGrid = split->Matrix->RedBlackGrid();
|
||||
return split;
|
||||
}
|
||||
};
|
||||
template<class Matrix,class Field> using SchurStagOperator = SchurStaggeredOperator<Matrix,Field>;
|
||||
@@ -521,7 +756,7 @@ template<class Field> class OperatorFunction {
|
||||
public:
|
||||
virtual void operator() (LinearOperatorBase<Field> &Linop, const Field &in, Field &out) = 0;
|
||||
virtual void operator() (LinearOperatorBase<Field> &Linop, const std::vector<Field> &in,std::vector<Field> &out) {
|
||||
assert(in.size()==out.size());
|
||||
GRID_ASSERT(in.size()==out.size());
|
||||
for(int k=0;k<in.size();k++){
|
||||
(*this)(Linop,in[k],out[k]);
|
||||
}
|
||||
@@ -535,13 +770,16 @@ public:
|
||||
|
||||
virtual void operator() (const std::vector<Field> &in, std::vector<Field> &out)
|
||||
{
|
||||
assert(in.size() == out.size());
|
||||
GRID_ASSERT(in.size() == out.size());
|
||||
|
||||
for (unsigned int i = 0; i < in.size(); ++i)
|
||||
{
|
||||
(*this)(in[i], out[i]);
|
||||
}
|
||||
}
|
||||
// The identity, if a derived class says so. A caller can then use its input
|
||||
// where it would otherwise have materialised the output, and skip the copy.
|
||||
virtual int isTrivial(void) { return 0; };
|
||||
virtual ~LinearFunction(){};
|
||||
};
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ public:
|
||||
virtual void operator()(const Field &src, Field & psi){
|
||||
psi = src;
|
||||
}
|
||||
virtual int isTrivial(void) { return 1; };
|
||||
TrivialPrecon(void){};
|
||||
};
|
||||
|
||||
|
||||
@@ -45,6 +45,11 @@ public:
|
||||
M(in,tmp);
|
||||
Mdag(tmp,out);
|
||||
}
|
||||
virtual void MMdag(const Field &in, Field &out) {
|
||||
Field tmp (in.Grid());
|
||||
Mdag(in,tmp);
|
||||
M(tmp,out);
|
||||
}
|
||||
virtual void Mdiag (const Field &in, Field &out)=0;
|
||||
virtual void Mdir (const Field &in, Field &out,int dir, int disp)=0;
|
||||
virtual void MdirAll (const Field &in, std::vector<Field> &out)=0;
|
||||
@@ -73,6 +78,17 @@ public:
|
||||
virtual void MeooeDag (const Field &in, Field &out)=0;
|
||||
virtual void MooeeDag (const Field &in, Field &out)=0;
|
||||
virtual void MooeeInvDag (const Field &in, Field &out)=0;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Copy of this operator on grids whose communicator is split into
|
||||
// partitions of MPI layout mpi_split. Collective over the full
|
||||
// communicator. nullptr if this operator cannot be cloned.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
virtual SplitOperator<Field> *SplitClone(const Coordinate &mpi_split)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual ~CheckerBoardedSparseMatrixBase() {};
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./lib/algorithms/SplitOperator.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
template<class Field> class LinearOperatorBase;
|
||||
template<class Field> class CheckerBoardedSparseMatrixBase;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// A copy of an operator living on grids whose communicator is split into independent
|
||||
// partitions, together with those grids. Produced by SplitClone().
|
||||
//
|
||||
// Owns everything it points to. Destruction is the reverse of creation: linear operator,
|
||||
// matrix, then grids, so no object outlives a grid it references. For 4d operators the
|
||||
// fermion grids are the gauge grids and are deleted once.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
template<class Field>
|
||||
class SplitOperator
|
||||
{
|
||||
public:
|
||||
GridCartesian *GaugeGrid = nullptr;
|
||||
GridRedBlackCartesian *GaugeRBGrid = nullptr;
|
||||
GridCartesian *FermionGrid = nullptr;
|
||||
GridRedBlackCartesian *FermionRBGrid = nullptr;
|
||||
|
||||
int Partition = 0; // Grid_split vector index held by this rank's partition
|
||||
int Partitions = 1; // number of partitions
|
||||
|
||||
CheckerBoardedSparseMatrixBase<Field> *Matrix = nullptr;
|
||||
LinearOperatorBase<Field> *Linop = nullptr;
|
||||
GridBase *FieldGrid = nullptr; // grid of Linop's fields
|
||||
|
||||
SplitOperator(void) {};
|
||||
|
||||
SplitOperator(const SplitOperator &) = delete;
|
||||
|
||||
SplitOperator &operator=(const SplitOperator &) = delete;
|
||||
|
||||
~SplitOperator(void)
|
||||
{
|
||||
delete Linop;
|
||||
delete Matrix;
|
||||
if ( FermionRBGrid != GaugeRBGrid ) {
|
||||
delete FermionRBGrid;
|
||||
}
|
||||
if ( FermionGrid != GaugeGrid ) {
|
||||
delete FermionGrid;
|
||||
}
|
||||
delete GaugeRBGrid;
|
||||
delete GaugeGrid;
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Index of the vector that Grid_split(std::vector<Field> full, Field split) delivers to this
|
||||
// rank's partition. Grid_split orders partitions lexicographically with the first
|
||||
// dimension fastest; the split communicator's own rank (srank) uses the reversed MPI
|
||||
// convention, so the two differ whenever more than one dimension is split.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
inline int GridSplitVectorIndex(GridBase *full,GridBase *split)
|
||||
{
|
||||
int nd = full->_ndimension;
|
||||
GRID_ASSERT(split->_ndimension == nd);
|
||||
|
||||
Coordinate scoor(nd);
|
||||
Coordinate ssize(nd);
|
||||
for(int d=0;d<nd;d++){
|
||||
scoor[d] = full->ThisProcessorCoor()[d] / split->ProcessorGrid()[d];
|
||||
ssize[d] = full->ProcessorGrid()[d] / split->ProcessorGrid()[d];
|
||||
}
|
||||
int index;
|
||||
Lexicographic::IndexFromCoor(scoor,index,ssize);
|
||||
return index;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Partition MPI layout for a batched solve on grid, from a request as given on the command
|
||||
// line (--batched-solver-split). Uses the trailing dimensions of grid's processor and shm
|
||||
// layouts, so it works for 4d and 5d grids. Returns grid's own processor layout (one
|
||||
// partition) when no split is requested. Asserts divisibility; warns when partitions
|
||||
// straddle nodes or when node boundaries are not visible.
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
inline Coordinate BatchedSolverSplitLayout(GridBase *grid,
|
||||
const Coordinate &request,
|
||||
bool node,
|
||||
int &partitions)
|
||||
{
|
||||
int nd = GridDefaultMpi().size();
|
||||
int pad = grid->_ndimension - nd;
|
||||
GRID_ASSERT(pad >= 0);
|
||||
GRID_ASSERT(grid->ShmGrid().size() == grid->_ndimension);
|
||||
|
||||
Coordinate processors(nd);
|
||||
Coordinate shm(nd);
|
||||
for(int d=0;d<nd;d++){
|
||||
processors[d] = grid->ProcessorGrid()[pad+d];
|
||||
shm[d] = grid->ShmGrid()[pad+d];
|
||||
}
|
||||
|
||||
Coordinate split(nd);
|
||||
if ( node ) {
|
||||
split = shm;
|
||||
} else if ( request.size() == 0 ) {
|
||||
split = processors;
|
||||
} else {
|
||||
GRID_ASSERT(request.size() == nd);
|
||||
split = request;
|
||||
}
|
||||
|
||||
partitions = 1;
|
||||
for(int d=0;d<nd;d++){
|
||||
GRID_ASSERT( (processors[d] % split[d]) == 0 );
|
||||
partitions *= processors[d] / split[d];
|
||||
}
|
||||
if ( partitions == 1 ) {
|
||||
return split;
|
||||
}
|
||||
|
||||
std::cout << GridLogMessage << "BatchedSolverSplit: partition layout " << split
|
||||
<< " of " << processors << " : " << partitions << " partitions" << std::endl;
|
||||
|
||||
int inside_node = 1;
|
||||
int whole_nodes = 1;
|
||||
int shm_trivial = 1;
|
||||
for(int d=0;d<nd;d++){
|
||||
if ( (shm[d] % split[d]) != 0 ) {
|
||||
inside_node = 0;
|
||||
}
|
||||
if ( (split[d] % shm[d]) != 0 ) {
|
||||
whole_nodes = 0;
|
||||
}
|
||||
if ( shm[d] != 1 ) {
|
||||
shm_trivial = 0;
|
||||
}
|
||||
}
|
||||
if ( shm_trivial ) {
|
||||
std::cout << GridLogWarning << "BatchedSolverSplit: shm layout is trivial (one rank per node,"
|
||||
<< " or shared memory disabled); node locality of partitions not checked" << std::endl;
|
||||
} else if ( !inside_node && !whole_nodes ) {
|
||||
std::cout << GridLogWarning << "BatchedSolverSplit: partitions " << split
|
||||
<< " straddle node boundaries (node layout " << shm << "); inner solves will communicate off node" << std::endl;
|
||||
}
|
||||
return split;
|
||||
}
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
RealD diff = hi-lo;
|
||||
RealD delta = diff*1.0e-9;
|
||||
for (RealD x=lo; x<hi; x+=delta) {
|
||||
delta*=1.1;
|
||||
delta*=1.02;
|
||||
RealD f = approx(x);
|
||||
out<< x<<" "<<f<<std::endl;
|
||||
}
|
||||
@@ -90,9 +90,8 @@ public:
|
||||
order=_order;
|
||||
|
||||
if(order < 2) exit(-1);
|
||||
Coeffs.resize(order);
|
||||
Coeffs.assign(0.,order);
|
||||
Coeffs[order-1] = 1.;
|
||||
Coeffs.resize(order,0.0);
|
||||
Coeffs[order-1] = 1.0;
|
||||
};
|
||||
|
||||
// PB - more efficient low pass drops high modes above the low as 1/x uses all Chebyshev's.
|
||||
@@ -132,6 +131,26 @@ public:
|
||||
Coeffs[j] = s * 2.0/order;
|
||||
}
|
||||
};
|
||||
template<class functor>
|
||||
void Init(RealD _lo,RealD _hi,int _order, functor & func)
|
||||
{
|
||||
lo=_lo;
|
||||
hi=_hi;
|
||||
order=_order;
|
||||
|
||||
if(order < 2) exit(-1);
|
||||
Coeffs.resize(order);
|
||||
for(int j=0;j<order;j++){
|
||||
RealD s=0;
|
||||
for(int k=0;k<order;k++){
|
||||
RealD y=std::cos(M_PI*(k+0.5)/order);
|
||||
RealD x=0.5*(y*(hi-lo)+(hi+lo));
|
||||
RealD f=func(x);
|
||||
s=s+f*std::cos( j*M_PI*(k+0.5)/order );
|
||||
}
|
||||
Coeffs[j] = s * 2.0/order;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void JacksonSmooth(void){
|
||||
@@ -250,7 +269,9 @@ public:
|
||||
RealD xscale = 2.0/(hi-lo);
|
||||
RealD mscale = -(hi+lo)/(hi-lo);
|
||||
Linop.HermOp(T0,y);
|
||||
grid->Barrier();
|
||||
axpby(T1,xscale,mscale,y,in);
|
||||
grid->Barrier();
|
||||
|
||||
// sum = .5 c[0] T0 + c[1] T1
|
||||
// out = ()*T0 + Coeffs[1]*T1;
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
RealD norm;
|
||||
RealD lo,hi;
|
||||
|
||||
MultiShiftFunction(int n,RealD _lo,RealD _hi): poles(n), residues(n), lo(_lo), hi(_hi) {;};
|
||||
MultiShiftFunction(int n,RealD _lo,RealD _hi): poles(n), residues(n), tolerances(n), lo(_lo), hi(_hi) {;};
|
||||
RealD approx(RealD x);
|
||||
void csv(std::ostream &out);
|
||||
void gnuplot(std::ostream &out);
|
||||
|
||||
@@ -121,7 +121,7 @@ double AlgRemez::generateApprox(int num_degree, int den_degree,
|
||||
// Reallocate arrays, since degree has changed
|
||||
if (num_degree != n || den_degree != d) allocate(num_degree,den_degree);
|
||||
|
||||
assert(a_len<=SUM_MAX);
|
||||
GRID_ASSERT(a_len<=SUM_MAX);
|
||||
|
||||
step = new bigfloat[num_degree+den_degree+2];
|
||||
|
||||
@@ -151,9 +151,9 @@ double AlgRemez::generateApprox(int num_degree, int den_degree,
|
||||
equations();
|
||||
if (delta < tolerance) {
|
||||
std::cout<<"Delta too small, try increasing precision\n";
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
};
|
||||
assert( delta>= tolerance);
|
||||
GRID_ASSERT( delta>= tolerance);
|
||||
|
||||
search(step);
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ class AlgRemez
|
||||
virtual ~AlgRemez();
|
||||
|
||||
int getDegree(void){
|
||||
assert(n==d);
|
||||
GRID_ASSERT(n==d);
|
||||
return n;
|
||||
}
|
||||
// Reset the bounds of the approximation
|
||||
|
||||
@@ -28,11 +28,11 @@ void AlgRemezGeneral::setupPolyProperties(int num_degree, int den_degree, PolyTy
|
||||
pow_n = num_degree;
|
||||
pow_d = den_degree;
|
||||
|
||||
if(pow_n % 2 == 0 && num_type_in == PolyType::Odd) assert(0);
|
||||
if(pow_n % 2 == 1 && num_type_in == PolyType::Even) assert(0);
|
||||
if(pow_n % 2 == 0 && num_type_in == PolyType::Odd) GRID_ASSERT(0);
|
||||
if(pow_n % 2 == 1 && num_type_in == PolyType::Even) GRID_ASSERT(0);
|
||||
|
||||
if(pow_d % 2 == 0 && den_type_in == PolyType::Odd) assert(0);
|
||||
if(pow_d % 2 == 1 && den_type_in == PolyType::Even) assert(0);
|
||||
if(pow_d % 2 == 0 && den_type_in == PolyType::Odd) GRID_ASSERT(0);
|
||||
if(pow_d % 2 == 1 && den_type_in == PolyType::Even) GRID_ASSERT(0);
|
||||
|
||||
num_type = num_type_in;
|
||||
den_type = den_type_in;
|
||||
@@ -112,9 +112,9 @@ double AlgRemezGeneral::generateApprox(const int num_degree, const int den_degre
|
||||
equations();
|
||||
if (delta < tolerance) {
|
||||
std::cout<<"Iteration " << iter-1 << " delta too small (" << delta << "<" << tolerance << "), try increasing precision\n";
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
};
|
||||
assert( delta>= tolerance );
|
||||
GRID_ASSERT( delta>= tolerance );
|
||||
|
||||
search();
|
||||
}
|
||||
@@ -278,7 +278,7 @@ void AlgRemezGeneral::equations(){
|
||||
if(num_pows[j] != -1){ *aa++ = z; t++; }
|
||||
z *= x;
|
||||
}
|
||||
assert(t == n+1);
|
||||
GRID_ASSERT(t == n+1);
|
||||
|
||||
z = (bigfloat)1l;
|
||||
t = 0;
|
||||
@@ -286,7 +286,7 @@ void AlgRemezGeneral::equations(){
|
||||
if(den_pows[j] != -1){ *aa++ = -y * z; t++; }
|
||||
z *= x;
|
||||
}
|
||||
assert(t == d);
|
||||
GRID_ASSERT(t == d);
|
||||
|
||||
B[i] = y * z; // Right hand side vector
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class AlgRemezGeneral{
|
||||
bigfloat (*f)(bigfloat x, void *data), void *data);
|
||||
|
||||
inline int getDegree(void) const{
|
||||
assert(n==d);
|
||||
GRID_ASSERT(n==d);
|
||||
return n;
|
||||
}
|
||||
// Reset the bounds of the approximation
|
||||
|
||||
@@ -74,7 +74,7 @@ bigfloat epsilonMobius(bigfloat x, void* data){
|
||||
void computeZmobiusOmega(std::vector<ComplexD> &omega_out, const int Ls_out,
|
||||
const std::vector<RealD> &omega_in, const int Ls_in,
|
||||
const RealD lambda_bound){
|
||||
assert(omega_in.size() == Ls_in);
|
||||
GRID_ASSERT(omega_in.size() == Ls_in);
|
||||
omega_out.resize(Ls_out);
|
||||
|
||||
//Use the Remez algorithm to generate the appropriate rational polynomial
|
||||
|
||||
@@ -293,7 +293,7 @@ static void sncndnFK(INTERNAL_PRECISION u, INTERNAL_PRECISION k,
|
||||
* Set type = 0 for the Zolotarev approximation, which is zero at x = 0, and
|
||||
* type = 1 for the approximation which is infinite at x = 0. */
|
||||
|
||||
zolotarev_data* zolotarev(PRECISION epsilon, int n, int type) {
|
||||
zolotarev_data* zolotarev(ZOLO_PRECISION epsilon, int n, int type) {
|
||||
INTERNAL_PRECISION A, c, cp, kp, ksq, sn, cn, dn, Kp, Kj, z, z0, t, M, F,
|
||||
l, invlambda, xi, xisq, *tv, s, opl;
|
||||
int m, czero, ts;
|
||||
@@ -375,12 +375,12 @@ zolotarev_data* zolotarev(PRECISION epsilon, int n, int type) {
|
||||
construct_partfrac(d);
|
||||
construct_contfrac(d);
|
||||
|
||||
/* Converting everything to PRECISION for external use only */
|
||||
/* Converting everything to ZOLO_PRECISION for external use only */
|
||||
|
||||
zd = (zolotarev_data*) malloc(sizeof(zolotarev_data));
|
||||
zd -> A = (PRECISION) d -> A;
|
||||
zd -> Delta = (PRECISION) d -> Delta;
|
||||
zd -> epsilon = (PRECISION) d -> epsilon;
|
||||
zd -> A = (ZOLO_PRECISION) d -> A;
|
||||
zd -> Delta = (ZOLO_PRECISION) d -> Delta;
|
||||
zd -> epsilon = (ZOLO_PRECISION) d -> epsilon;
|
||||
zd -> n = d -> n;
|
||||
zd -> type = d -> type;
|
||||
zd -> dn = d -> dn;
|
||||
@@ -390,24 +390,24 @@ zolotarev_data* zolotarev(PRECISION epsilon, int n, int type) {
|
||||
zd -> deg_num = d -> deg_num;
|
||||
zd -> deg_denom = d -> deg_denom;
|
||||
|
||||
zd -> a = (PRECISION*) malloc(zd -> dn * sizeof(PRECISION));
|
||||
for (m = 0; m < zd -> dn; m++) zd -> a[m] = (PRECISION) d -> a[m];
|
||||
zd -> a = (ZOLO_PRECISION*) malloc(zd -> dn * sizeof(ZOLO_PRECISION));
|
||||
for (m = 0; m < zd -> dn; m++) zd -> a[m] = (ZOLO_PRECISION) d -> a[m];
|
||||
free(d -> a);
|
||||
|
||||
zd -> ap = (PRECISION*) malloc(zd -> dd * sizeof(PRECISION));
|
||||
for (m = 0; m < zd -> dd; m++) zd -> ap[m] = (PRECISION) d -> ap[m];
|
||||
zd -> ap = (ZOLO_PRECISION*) malloc(zd -> dd * sizeof(ZOLO_PRECISION));
|
||||
for (m = 0; m < zd -> dd; m++) zd -> ap[m] = (ZOLO_PRECISION) d -> ap[m];
|
||||
free(d -> ap);
|
||||
|
||||
zd -> alpha = (PRECISION*) malloc(zd -> da * sizeof(PRECISION));
|
||||
for (m = 0; m < zd -> da; m++) zd -> alpha[m] = (PRECISION) d -> alpha[m];
|
||||
zd -> alpha = (ZOLO_PRECISION*) malloc(zd -> da * sizeof(ZOLO_PRECISION));
|
||||
for (m = 0; m < zd -> da; m++) zd -> alpha[m] = (ZOLO_PRECISION) d -> alpha[m];
|
||||
free(d -> alpha);
|
||||
|
||||
zd -> beta = (PRECISION*) malloc(zd -> db * sizeof(PRECISION));
|
||||
for (m = 0; m < zd -> db; m++) zd -> beta[m] = (PRECISION) d -> beta[m];
|
||||
zd -> beta = (ZOLO_PRECISION*) malloc(zd -> db * sizeof(ZOLO_PRECISION));
|
||||
for (m = 0; m < zd -> db; m++) zd -> beta[m] = (ZOLO_PRECISION) d -> beta[m];
|
||||
free(d -> beta);
|
||||
|
||||
zd -> gamma = (PRECISION*) malloc(zd -> n * sizeof(PRECISION));
|
||||
for (m = 0; m < zd -> n; m++) zd -> gamma[m] = (PRECISION) d -> gamma[m];
|
||||
zd -> gamma = (ZOLO_PRECISION*) malloc(zd -> n * sizeof(ZOLO_PRECISION));
|
||||
for (m = 0; m < zd -> n; m++) zd -> gamma[m] = (ZOLO_PRECISION) d -> gamma[m];
|
||||
free(d -> gamma);
|
||||
|
||||
free(d);
|
||||
@@ -426,7 +426,7 @@ void zolotarev_free(zolotarev_data *zdata)
|
||||
}
|
||||
|
||||
|
||||
zolotarev_data* higham(PRECISION epsilon, int n) {
|
||||
zolotarev_data* higham(ZOLO_PRECISION epsilon, int n) {
|
||||
INTERNAL_PRECISION A, M, c, cp, z, z0, t, epssq;
|
||||
int m, czero;
|
||||
zolotarev_data *zd;
|
||||
@@ -481,9 +481,9 @@ zolotarev_data* higham(PRECISION epsilon, int n) {
|
||||
/* Converting everything to PRECISION for external use only */
|
||||
|
||||
zd = (zolotarev_data*) malloc(sizeof(zolotarev_data));
|
||||
zd -> A = (PRECISION) d -> A;
|
||||
zd -> Delta = (PRECISION) d -> Delta;
|
||||
zd -> epsilon = (PRECISION) d -> epsilon;
|
||||
zd -> A = (ZOLO_PRECISION) d -> A;
|
||||
zd -> Delta = (ZOLO_PRECISION) d -> Delta;
|
||||
zd -> epsilon = (ZOLO_PRECISION) d -> epsilon;
|
||||
zd -> n = d -> n;
|
||||
zd -> type = d -> type;
|
||||
zd -> dn = d -> dn;
|
||||
@@ -493,24 +493,24 @@ zolotarev_data* higham(PRECISION epsilon, int n) {
|
||||
zd -> deg_num = d -> deg_num;
|
||||
zd -> deg_denom = d -> deg_denom;
|
||||
|
||||
zd -> a = (PRECISION*) malloc(zd -> dn * sizeof(PRECISION));
|
||||
for (m = 0; m < zd -> dn; m++) zd -> a[m] = (PRECISION) d -> a[m];
|
||||
zd -> a = (ZOLO_PRECISION*) malloc(zd -> dn * sizeof(ZOLO_PRECISION));
|
||||
for (m = 0; m < zd -> dn; m++) zd -> a[m] = (ZOLO_PRECISION) d -> a[m];
|
||||
free(d -> a);
|
||||
|
||||
zd -> ap = (PRECISION*) malloc(zd -> dd * sizeof(PRECISION));
|
||||
for (m = 0; m < zd -> dd; m++) zd -> ap[m] = (PRECISION) d -> ap[m];
|
||||
zd -> ap = (ZOLO_PRECISION*) malloc(zd -> dd * sizeof(ZOLO_PRECISION));
|
||||
for (m = 0; m < zd -> dd; m++) zd -> ap[m] = (ZOLO_PRECISION) d -> ap[m];
|
||||
free(d -> ap);
|
||||
|
||||
zd -> alpha = (PRECISION*) malloc(zd -> da * sizeof(PRECISION));
|
||||
for (m = 0; m < zd -> da; m++) zd -> alpha[m] = (PRECISION) d -> alpha[m];
|
||||
zd -> alpha = (ZOLO_PRECISION*) malloc(zd -> da * sizeof(ZOLO_PRECISION));
|
||||
for (m = 0; m < zd -> da; m++) zd -> alpha[m] = (ZOLO_PRECISION) d -> alpha[m];
|
||||
free(d -> alpha);
|
||||
|
||||
zd -> beta = (PRECISION*) malloc(zd -> db * sizeof(PRECISION));
|
||||
for (m = 0; m < zd -> db; m++) zd -> beta[m] = (PRECISION) d -> beta[m];
|
||||
zd -> beta = (ZOLO_PRECISION*) malloc(zd -> db * sizeof(ZOLO_PRECISION));
|
||||
for (m = 0; m < zd -> db; m++) zd -> beta[m] = (ZOLO_PRECISION) d -> beta[m];
|
||||
free(d -> beta);
|
||||
|
||||
zd -> gamma = (PRECISION*) malloc(zd -> n * sizeof(PRECISION));
|
||||
for (m = 0; m < zd -> n; m++) zd -> gamma[m] = (PRECISION) d -> gamma[m];
|
||||
zd -> gamma = (ZOLO_PRECISION*) malloc(zd -> n * sizeof(ZOLO_PRECISION));
|
||||
for (m = 0; m < zd -> n; m++) zd -> gamma[m] = (ZOLO_PRECISION) d -> gamma[m];
|
||||
free(d -> gamma);
|
||||
|
||||
free(d);
|
||||
@@ -523,17 +523,17 @@ NAMESPACE_END(Grid);
|
||||
#ifdef TEST
|
||||
|
||||
#undef ZERO
|
||||
#define ZERO ((PRECISION) 0)
|
||||
#define ZERO ((ZOLO_PRECISION) 0)
|
||||
#undef ONE
|
||||
#define ONE ((PRECISION) 1)
|
||||
#define ONE ((ZOLO_PRECISION) 1)
|
||||
#undef TWO
|
||||
#define TWO ((PRECISION) 2)
|
||||
#define TWO ((ZOLO_PRECISION) 2)
|
||||
|
||||
/* Evaluate the rational approximation R(x) using the factored form */
|
||||
|
||||
static PRECISION zolotarev_eval(PRECISION x, zolotarev_data* rdata) {
|
||||
static ZOLO_PRECISION zolotarev_eval(ZOLO_PRECISION x, zolotarev_data* rdata) {
|
||||
int m;
|
||||
PRECISION R;
|
||||
ZOLO_PRECISION R;
|
||||
|
||||
if (rdata -> type == 0) {
|
||||
R = rdata -> A * x;
|
||||
@@ -551,9 +551,9 @@ static PRECISION zolotarev_eval(PRECISION x, zolotarev_data* rdata) {
|
||||
|
||||
/* Evaluate the rational approximation R(x) using the partial fraction form */
|
||||
|
||||
static PRECISION zolotarev_partfrac_eval(PRECISION x, zolotarev_data* rdata) {
|
||||
static ZOLO_PRECISION zolotarev_partfrac_eval(ZOLO_PRECISION x, zolotarev_data* rdata) {
|
||||
int m;
|
||||
PRECISION R = rdata -> alpha[rdata -> da - 1];
|
||||
ZOLO_PRECISION R = rdata -> alpha[rdata -> da - 1];
|
||||
for (m = 0; m < rdata -> dd; m++)
|
||||
R += rdata -> alpha[m] / (x * x - rdata -> ap[m]);
|
||||
if (rdata -> type == 1) R += rdata -> alpha[rdata -> dd] / (x * x);
|
||||
@@ -568,18 +568,18 @@ static PRECISION zolotarev_partfrac_eval(PRECISION x, zolotarev_data* rdata) {
|
||||
* non-signalling overflow this will work correctly since 1/(1/0) = 1/INF = 0,
|
||||
* but with signalling overflow you will get an error message. */
|
||||
|
||||
static PRECISION zolotarev_contfrac_eval(PRECISION x, zolotarev_data* rdata) {
|
||||
static ZOLO_PRECISION zolotarev_contfrac_eval(ZOLO_PRECISION x, zolotarev_data* rdata) {
|
||||
int m;
|
||||
PRECISION R = rdata -> beta[0] * x;
|
||||
ZOLO_PRECISION R = rdata -> beta[0] * x;
|
||||
for (m = 1; m < rdata -> db; m++) R = rdata -> beta[m] * x + ONE / R;
|
||||
return R;
|
||||
}
|
||||
|
||||
/* Evaluate the rational approximation R(x) using Cayley form */
|
||||
|
||||
static PRECISION zolotarev_cayley_eval(PRECISION x, zolotarev_data* rdata) {
|
||||
static ZOLO_PRECISION zolotarev_cayley_eval(ZOLO_PRECISION x, zolotarev_data* rdata) {
|
||||
int m;
|
||||
PRECISION T;
|
||||
ZOLO_PRECISION T;
|
||||
|
||||
T = rdata -> type == 0 ? ONE : -ONE;
|
||||
for (m = 0; m < rdata -> n; m++)
|
||||
@@ -607,7 +607,7 @@ int main(int argc, char** argv) {
|
||||
int m, n, plotpts = 5000, type = 0;
|
||||
float eps, x, ypferr, ycferr, ycaylerr, maxypferr, maxycferr, maxycaylerr;
|
||||
zolotarev_data *rdata;
|
||||
PRECISION y;
|
||||
ZOLO_PRECISION y;
|
||||
FILE *plot_function, *plot_error,
|
||||
*plot_partfrac, *plot_contfrac, *plot_cayley;
|
||||
|
||||
@@ -626,13 +626,13 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
rdata = type == 2
|
||||
? higham((PRECISION) eps, n)
|
||||
: zolotarev((PRECISION) eps, n, type);
|
||||
? higham((ZOLO_PRECISION) eps, n)
|
||||
: zolotarev((ZOLO_PRECISION) eps, n, type);
|
||||
|
||||
printf("Zolotarev Test: R(epsilon = %g, n = %d, type = %d)\n\t"
|
||||
STRINGIFY(VERSION) "\n\t" STRINGIFY(HVERSION)
|
||||
"\n\tINTERNAL_PRECISION = " STRINGIFY(INTERNAL_PRECISION)
|
||||
"\tPRECISION = " STRINGIFY(PRECISION)
|
||||
"\tZOLO_PRECISION = " STRINGIFY(ZOLO_PRECISION)
|
||||
"\n\n\tRational approximation of degree (%d,%d), %s at x = 0\n"
|
||||
"\tDelta = %g (maximum error)\n\n"
|
||||
"\tA = %g (overall factor)\n",
|
||||
@@ -681,15 +681,15 @@ int main(int argc, char** argv) {
|
||||
x = 2.4 * (float) m / plotpts - 1.2;
|
||||
if (rdata -> type == 0 || fabs(x) * (float) plotpts > 1.0) {
|
||||
/* skip x = 0 for type 1, as R(0) is singular */
|
||||
y = zolotarev_eval((PRECISION) x, rdata);
|
||||
y = zolotarev_eval((ZOLO_PRECISION) x, rdata);
|
||||
fprintf(plot_function, "%g %g\n", x, (float) y);
|
||||
fprintf(plot_error, "%g %g\n",
|
||||
x, (float)((y - ((x > 0.0 ? ONE : -ONE))) / rdata -> Delta));
|
||||
ypferr = (float)((zolotarev_partfrac_eval((PRECISION) x, rdata) - y)
|
||||
ypferr = (float)((zolotarev_partfrac_eval((ZOLO_PRECISION) x, rdata) - y)
|
||||
/ rdata -> Delta);
|
||||
ycferr = (float)((zolotarev_contfrac_eval((PRECISION) x, rdata) - y)
|
||||
ycferr = (float)((zolotarev_contfrac_eval((ZOLO_PRECISION) x, rdata) - y)
|
||||
/ rdata -> Delta);
|
||||
ycaylerr = (float)((zolotarev_cayley_eval((PRECISION) x, rdata) - y)
|
||||
ycaylerr = (float)((zolotarev_cayley_eval((ZOLO_PRECISION) x, rdata) - y)
|
||||
/ rdata -> Delta);
|
||||
if (fabs(x) < 1.0 && fabs(x) > rdata -> epsilon) {
|
||||
maxypferr = MAX(maxypferr, fabs(ypferr));
|
||||
|
||||
@@ -9,10 +9,10 @@ NAMESPACE_BEGIN(Approx);
|
||||
#define HVERSION Header Time-stamp: <14-OCT-2004 09:26:51.00 adk@MISSCONTRARY>
|
||||
|
||||
#ifndef ZOLOTAREV_INTERNAL
|
||||
#ifndef PRECISION
|
||||
#define PRECISION double
|
||||
#ifndef ZOLO_PRECISION
|
||||
#define ZOLO_PRECISION double
|
||||
#endif
|
||||
#define ZPRECISION PRECISION
|
||||
#define ZPRECISION ZOLO_PRECISION
|
||||
#define ZOLOTAREV_DATA zolotarev_data
|
||||
#endif
|
||||
|
||||
@@ -77,8 +77,8 @@ typedef struct {
|
||||
* zolotarev_data structure. The arguments must satisfy the constraints that
|
||||
* epsilon > 0, n > 0, and type = 0 or 1. */
|
||||
|
||||
ZOLOTAREV_DATA* higham(PRECISION epsilon, int n) ;
|
||||
ZOLOTAREV_DATA* zolotarev(PRECISION epsilon, int n, int type);
|
||||
ZOLOTAREV_DATA* higham(ZOLO_PRECISION epsilon, int n) ;
|
||||
ZOLOTAREV_DATA* zolotarev(ZOLO_PRECISION epsilon, int n, int type);
|
||||
void zolotarev_free(zolotarev_data *zdata);
|
||||
#endif
|
||||
|
||||
@@ -86,3 +86,4 @@ void zolotarev_free(zolotarev_data *zdata);
|
||||
NAMESPACE_END(Approx);
|
||||
NAMESPACE_END(Grid);
|
||||
#endif
|
||||
|
||||
+1493
-225
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,362 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: BatchedInverse.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
#include <Grid/algorithms/blas/BatchedBlas.h>
|
||||
|
||||
#ifdef GRID_HIP
|
||||
#include <rocsolver/rocsolver.h>
|
||||
#endif
|
||||
// GRID_CUDA: batched LU inversion lives in cuBLAS (getrfBatched/getriBatched);
|
||||
// cublas_v2.h already included via BatchedBlas.h.
|
||||
// GRID_SYCL: oneapi/mkl.hpp already included via BatchedBlas.h (lapack::getrf/getri).
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// GridBLASInverse: cross-platform batched dense matrix inversion.
|
||||
//
|
||||
// HIGH LEVEL contract (deliberately NOT a getrf/getrs interface): invert a
|
||||
// batch of dense N x N matrices IN PLACE,
|
||||
//
|
||||
// A[i] <- A[i]^{-1} i = 0 .. batchCount-1
|
||||
//
|
||||
// Layout: column major, lda = N, contiguous per batch element; pointer list
|
||||
// exactly as GridBLAS::gemmBatched (deviceVector<T*> of device pointers).
|
||||
// Each backend chooses HOW:
|
||||
// HIP : rocSOLVER getrf_batched + getri_batched
|
||||
// CUDA : cuBLAS getrfBatched + getriBatched (out-of-place getri; workspace
|
||||
// hidden here, result copied back so the surface stays in-place)
|
||||
// SYCL : oneMKL LAPACK getrf + getri per batch element (USM, in-order queue)
|
||||
// CPU : Eigen PartialPivLU (the correctness oracle for all of the above)
|
||||
//
|
||||
// The int32 vendor-batched entry points bound N < 2^31 (asserted); the huge
|
||||
// single-matrix ILP64 path (getrf_64 + blocked identity-getrs harvest, proven
|
||||
// in the dense coarse-coarse setup at N=69120) migrates here as a batch==1
|
||||
// large-N dispatch in a follow-up -- the recursive Schur leaves are the
|
||||
// batched consumers this surface is shaped for.
|
||||
//
|
||||
// NB GPU-backend call signatures are written to vendor documentation but the
|
||||
// air-gapped development loop compiles only the CPU/Eigen path; verify the
|
||||
// rocSOLVER/cuBLAS/oneMKL calls against headers on first device compile.
|
||||
// Semantics are locked by the CPU unit test (Test_batched_blas).
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class GridBLASInverse {
|
||||
public:
|
||||
|
||||
#ifdef GRID_HIP
|
||||
// rocSOLVER runs on a rocblas_handle (distinct type from hipblasHandle_t)
|
||||
static rocblas_handle & Handle(void) {
|
||||
static rocblas_handle h;
|
||||
static int init = 0;
|
||||
if ( !init ) {
|
||||
auto st = rocblas_create_handle(&h);
|
||||
GRID_ASSERT(st == rocblas_status_success);
|
||||
init = 1;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
#endif
|
||||
#ifdef GRID_CUDA
|
||||
// cuBLAS batched LU shares the GridBLAS handle
|
||||
static cublasHandle_t & Handle(void) {
|
||||
GridBLAS::Init();
|
||||
return GridBLAS::gridblasHandle;
|
||||
}
|
||||
#endif
|
||||
#ifdef GRID_SYCL
|
||||
static sycl::queue * & Handle(void) {
|
||||
GridBLAS::Init();
|
||||
return GridBLAS::gridblasHandle;
|
||||
}
|
||||
#endif
|
||||
|
||||
GridBLASInverse() {};
|
||||
~GridBLASInverse() {};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// SINGLE large matrix, in place: blocked LU + identity solve.
|
||||
// HIP : rocsolver_zgetrf_64 (blocked, GEMM-based) then ONE zgetrs_64 with
|
||||
// the N x N identity as RHS (trsm-based) into a scratch N x N, copied
|
||||
// back. Extra device memory: N*N*16 B (1.2 GB at N=8640).
|
||||
// other backends: falls through to inverseBatched(batch 1).
|
||||
// Why: rocSOLVER getri_batched is a small-matrix routine -- measured 0.35 s
|
||||
// at N=1920, 0.53 s at 4320, 1.84 s at 8640 (n^1.2-1.8, i.e. overhead
|
||||
// bound) as the big-leaf inverse of the 2D Schur recursion, where 287
|
||||
// ranks wait on it. The _64 getrf/getrs pair is the path proven in the
|
||||
// 1-rank dense coarse-coarse setup at N=69120 (DenseCoarseMatrix.h).
|
||||
// Same in-place, column-major, lda=N contract as inverseBatched.
|
||||
// ComplexD and ComplexF overloads (z*_64 / c*_64), so the distributed
|
||||
// dense inverse can run in either precision (DenseInverseScalar).
|
||||
// NB: written to rocSOLVER's documented *_64 signatures; not compiled on
|
||||
// HIP in the air-gapped loop -- verify on first hipcc build.
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
double lastGetrfUs = 0.0, lastGetrsUs = 0.0; // split timing of the last inverseLU (HIP path)
|
||||
void inverseLU(int64_t N, ComplexD *A)
|
||||
{
|
||||
#ifdef GRID_HIP
|
||||
rocblas_handle handle = Handle();
|
||||
deviceVector<int64_t> ipiv((uint64_t)N);
|
||||
deviceVector<int64_t> info(1);
|
||||
double t0 = usecond();
|
||||
auto st1 = rocsolver_zgetrf_64(handle, N, N, (rocblas_double_complex *)A, N, &ipiv[0], &info[0]);
|
||||
GRID_ASSERT(st1 == rocblas_status_success);
|
||||
accelerator_barrier();
|
||||
lastGetrfUs = usecond()-t0;
|
||||
int64_t info_h = -1; acceleratorCopyFromDevice(&info[0], &info_h, sizeof(int64_t));
|
||||
GRID_ASSERT(info_h == 0);
|
||||
deviceVector<ComplexD> X((uint64_t)N*N);
|
||||
{ ComplexD *x = &X[0]; const int64_t NN = N;
|
||||
accelerator_for(idx, (uint64_t)N*N, 1, { int64_t j = idx/NN, i = idx - j*NN; x[idx] = (i==j) ? ComplexD(1.0,0.0) : ComplexD(0.0,0.0); });
|
||||
accelerator_barrier(); }
|
||||
auto st2 = rocsolver_zgetrs_64(handle, rocblas_operation_none, N, N,
|
||||
(rocblas_double_complex *)A, N, &ipiv[0],
|
||||
(rocblas_double_complex *)&X[0], N);
|
||||
GRID_ASSERT(st2 == rocblas_status_success);
|
||||
accelerator_barrier();
|
||||
lastGetrsUs = usecond()-t0-lastGetrfUs;
|
||||
acceleratorCopyDeviceToDevice((void *)&X[0], (void *)A, (uint64_t)N*N*sizeof(ComplexD));
|
||||
#else
|
||||
deviceVector<ComplexD*> bp(1); std::vector<ComplexD*> ptr(1); ptr[0] = A;
|
||||
acceleratorCopyToDevice(&ptr[0], &bp[0], sizeof(ComplexD*));
|
||||
inverseBatched(N, bp);
|
||||
#endif
|
||||
}
|
||||
// fp32 overload: the Schur-recursion leaf under --enable-dense-inverse-precision=single
|
||||
void inverseLU(int64_t N, ComplexF *A)
|
||||
{
|
||||
#ifdef GRID_HIP
|
||||
rocblas_handle handle = Handle();
|
||||
deviceVector<int64_t> ipiv((uint64_t)N);
|
||||
deviceVector<int64_t> info(1);
|
||||
double t0 = usecond();
|
||||
auto st1 = rocsolver_cgetrf_64(handle, N, N, (rocblas_float_complex *)A, N, &ipiv[0], &info[0]);
|
||||
GRID_ASSERT(st1 == rocblas_status_success);
|
||||
accelerator_barrier();
|
||||
lastGetrfUs = usecond()-t0;
|
||||
int64_t info_h = -1; acceleratorCopyFromDevice(&info[0], &info_h, sizeof(int64_t));
|
||||
GRID_ASSERT(info_h == 0);
|
||||
deviceVector<ComplexF> X((uint64_t)N*N);
|
||||
{ ComplexF *x = &X[0]; const int64_t NN = N;
|
||||
accelerator_for(idx, (uint64_t)N*N, 1, { int64_t j = idx/NN, i = idx - j*NN; x[idx] = (i==j) ? ComplexF(1.0,0.0) : ComplexF(0.0,0.0); });
|
||||
accelerator_barrier(); }
|
||||
auto st2 = rocsolver_cgetrs_64(handle, rocblas_operation_none, N, N,
|
||||
(rocblas_float_complex *)A, N, &ipiv[0],
|
||||
(rocblas_float_complex *)&X[0], N);
|
||||
GRID_ASSERT(st2 == rocblas_status_success);
|
||||
accelerator_barrier();
|
||||
lastGetrsUs = usecond()-t0-lastGetrfUs;
|
||||
acceleratorCopyDeviceToDevice((void *)&X[0], (void *)A, (uint64_t)N*N*sizeof(ComplexF));
|
||||
#else
|
||||
deviceVector<ComplexF*> bp(1); std::vector<ComplexF*> ptr(1); ptr[0] = A;
|
||||
acceleratorCopyToDevice(&ptr[0], &bp[0], sizeof(ComplexF*));
|
||||
inverseBatched(N, bp);
|
||||
#endif
|
||||
}
|
||||
|
||||
void inverseBatched(int64_t N, deviceVector<ComplexF*> &Amat)
|
||||
{
|
||||
int32_t batchCount = Amat.size();
|
||||
GRID_ASSERT(batchCount > 0);
|
||||
|
||||
#ifdef GRID_HIP
|
||||
GRID_ASSERT( N < 2147483647L );
|
||||
rocblas_int n = (rocblas_int)N;
|
||||
rocblas_int lda = (rocblas_int)N;
|
||||
|
||||
deviceVector<rocblas_int> ipiv((uint64_t)batchCount*N);
|
||||
deviceVector<rocblas_int> info(batchCount);
|
||||
|
||||
auto st1 = rocsolver_cgetrf_batched(Handle(), n, n,
|
||||
(rocblas_float_complex *const *)&Amat[0], lda,
|
||||
&ipiv[0], (rocblas_stride)N,
|
||||
&info[0], batchCount);
|
||||
GRID_ASSERT(st1 == rocblas_status_success);
|
||||
auto st2 = rocsolver_cgetri_batched(Handle(), n,
|
||||
(rocblas_float_complex *const *)&Amat[0], lda,
|
||||
&ipiv[0], (rocblas_stride)N,
|
||||
&info[0], batchCount);
|
||||
GRID_ASSERT(st2 == rocblas_status_success);
|
||||
accelerator_barrier();
|
||||
std::vector<rocblas_int> info_h(batchCount);
|
||||
acceleratorCopyFromDevice(&info[0],&info_h[0],batchCount*sizeof(rocblas_int));
|
||||
for(int i=0;i<batchCount;i++) GRID_ASSERT(info_h[i]==0); // singular pivot => abort loudly
|
||||
#endif
|
||||
#ifdef GRID_CUDA
|
||||
GRID_ASSERT( N < 2147483647L );
|
||||
int n = (int)N;
|
||||
|
||||
deviceVector<int> ipiv((uint64_t)batchCount*N);
|
||||
deviceVector<int> info(batchCount);
|
||||
|
||||
auto st1 = cublasCgetrfBatched(Handle(), n,
|
||||
(cuComplex **)&Amat[0], n,
|
||||
&ipiv[0], &info[0], batchCount);
|
||||
GRID_ASSERT(st1 == CUBLAS_STATUS_SUCCESS);
|
||||
|
||||
// getri is OUT of place: hidden workspace keeps the surface in-place
|
||||
deviceVector<ComplexF> work((uint64_t)batchCount*N*N);
|
||||
deviceVector<ComplexF*> Cptr(batchCount);
|
||||
std::vector<ComplexF*> Cptr_h(batchCount);
|
||||
std::vector<ComplexF*> Aptr_h(batchCount);
|
||||
for(int i=0;i<batchCount;i++) Cptr_h[i] = &work[(uint64_t)i*N*N];
|
||||
acceleratorCopyToDevice(&Cptr_h[0],&Cptr[0],batchCount*sizeof(ComplexF*));
|
||||
acceleratorCopyFromDevice(&Amat[0],&Aptr_h[0],batchCount*sizeof(ComplexF*));
|
||||
|
||||
auto st2 = cublasCgetriBatched(Handle(), n,
|
||||
(const cuComplex *const *)&Amat[0], n,
|
||||
&ipiv[0],
|
||||
(cuComplex **)&Cptr[0], n,
|
||||
&info[0], batchCount);
|
||||
GRID_ASSERT(st2 == CUBLAS_STATUS_SUCCESS);
|
||||
accelerator_barrier();
|
||||
std::vector<int> info_h(batchCount);
|
||||
acceleratorCopyFromDevice(&info[0],&info_h[0],batchCount*sizeof(int));
|
||||
for(int i=0;i<batchCount;i++) GRID_ASSERT(info_h[i]==0);
|
||||
for(int i=0;i<batchCount;i++)
|
||||
acceleratorCopyDeviceToDevice(Cptr_h[i],Aptr_h[i],(uint64_t)N*N*sizeof(ComplexF));
|
||||
#endif
|
||||
#ifdef GRID_SYCL
|
||||
// Per-element oneMKL LAPACK on the in-order queue; group API optimisation later.
|
||||
sycl::queue *q = Handle();
|
||||
std::vector<ComplexF*> Aptr_h(batchCount);
|
||||
acceleratorCopyFromDevice(&Amat[0],&Aptr_h[0],batchCount*sizeof(ComplexF*));
|
||||
|
||||
int64_t lwf = oneapi::mkl::lapack::getrf_scratchpad_size<std::complex<float> >(*q,N,N,N);
|
||||
int64_t lwi = oneapi::mkl::lapack::getri_scratchpad_size<std::complex<float> >(*q,N,N);
|
||||
deviceVector<ComplexF> scratchf(lwf);
|
||||
deviceVector<ComplexF> scratchi(lwi);
|
||||
deviceVector<int64_t> ipiv(N);
|
||||
for(int i=0;i<batchCount;i++){
|
||||
oneapi::mkl::lapack::getrf(*q,N,N,(std::complex<float>*)Aptr_h[i],N,&ipiv[0],
|
||||
(std::complex<float>*)&scratchf[0],lwf);
|
||||
oneapi::mkl::lapack::getri(*q,N, (std::complex<float>*)Aptr_h[i],N,&ipiv[0],
|
||||
(std::complex<float>*)&scratchi[0],lwi);
|
||||
}
|
||||
q->wait();
|
||||
#endif
|
||||
#if !defined(GRID_SYCL) && !defined(GRID_CUDA) && !defined(GRID_HIP)
|
||||
// Reference implementation; the oracle the unit test locks semantics with.
|
||||
thread_for (p, batchCount, {
|
||||
Eigen::Map<Eigen::MatrixXcf> eA(Amat[p],N,N);
|
||||
Eigen::PartialPivLU<Eigen::MatrixXcf> lu(eA);
|
||||
eA = lu.inverse();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void inverseBatched(int64_t N, deviceVector<ComplexD*> &Amat)
|
||||
{
|
||||
int32_t batchCount = Amat.size();
|
||||
GRID_ASSERT(batchCount > 0);
|
||||
|
||||
#ifdef GRID_HIP
|
||||
GRID_ASSERT( N < 2147483647L );
|
||||
rocblas_int n = (rocblas_int)N;
|
||||
rocblas_int lda = (rocblas_int)N;
|
||||
|
||||
deviceVector<rocblas_int> ipiv((uint64_t)batchCount*N);
|
||||
deviceVector<rocblas_int> info(batchCount);
|
||||
|
||||
auto st1 = rocsolver_zgetrf_batched(Handle(), n, n,
|
||||
(rocblas_double_complex *const *)&Amat[0], lda,
|
||||
&ipiv[0], (rocblas_stride)N,
|
||||
&info[0], batchCount);
|
||||
GRID_ASSERT(st1 == rocblas_status_success);
|
||||
auto st2 = rocsolver_zgetri_batched(Handle(), n,
|
||||
(rocblas_double_complex *const *)&Amat[0], lda,
|
||||
&ipiv[0], (rocblas_stride)N,
|
||||
&info[0], batchCount);
|
||||
GRID_ASSERT(st2 == rocblas_status_success);
|
||||
accelerator_barrier();
|
||||
std::vector<rocblas_int> info_h(batchCount);
|
||||
acceleratorCopyFromDevice(&info[0],&info_h[0],batchCount*sizeof(rocblas_int));
|
||||
for(int i=0;i<batchCount;i++) GRID_ASSERT(info_h[i]==0);
|
||||
#endif
|
||||
#ifdef GRID_CUDA
|
||||
GRID_ASSERT( N < 2147483647L );
|
||||
int n = (int)N;
|
||||
|
||||
deviceVector<int> ipiv((uint64_t)batchCount*N);
|
||||
deviceVector<int> info(batchCount);
|
||||
|
||||
auto st1 = cublasZgetrfBatched(Handle(), n,
|
||||
(cuDoubleComplex **)&Amat[0], n,
|
||||
&ipiv[0], &info[0], batchCount);
|
||||
GRID_ASSERT(st1 == CUBLAS_STATUS_SUCCESS);
|
||||
|
||||
deviceVector<ComplexD> work((uint64_t)batchCount*N*N);
|
||||
deviceVector<ComplexD*> Cptr(batchCount);
|
||||
std::vector<ComplexD*> Cptr_h(batchCount);
|
||||
std::vector<ComplexD*> Aptr_h(batchCount);
|
||||
for(int i=0;i<batchCount;i++) Cptr_h[i] = &work[(uint64_t)i*N*N];
|
||||
acceleratorCopyToDevice(&Cptr_h[0],&Cptr[0],batchCount*sizeof(ComplexD*));
|
||||
acceleratorCopyFromDevice(&Amat[0],&Aptr_h[0],batchCount*sizeof(ComplexD*));
|
||||
|
||||
auto st2 = cublasZgetriBatched(Handle(), n,
|
||||
(const cuDoubleComplex *const *)&Amat[0], n,
|
||||
&ipiv[0],
|
||||
(cuDoubleComplex **)&Cptr[0], n,
|
||||
&info[0], batchCount);
|
||||
GRID_ASSERT(st2 == CUBLAS_STATUS_SUCCESS);
|
||||
accelerator_barrier();
|
||||
std::vector<int> info_h(batchCount);
|
||||
acceleratorCopyFromDevice(&info[0],&info_h[0],batchCount*sizeof(int));
|
||||
for(int i=0;i<batchCount;i++) GRID_ASSERT(info_h[i]==0);
|
||||
for(int i=0;i<batchCount;i++)
|
||||
acceleratorCopyDeviceToDevice(Cptr_h[i],Aptr_h[i],(uint64_t)N*N*sizeof(ComplexD));
|
||||
#endif
|
||||
#ifdef GRID_SYCL
|
||||
sycl::queue *q = Handle();
|
||||
std::vector<ComplexD*> Aptr_h(batchCount);
|
||||
acceleratorCopyFromDevice(&Amat[0],&Aptr_h[0],batchCount*sizeof(ComplexD*));
|
||||
|
||||
int64_t lwf = oneapi::mkl::lapack::getrf_scratchpad_size<std::complex<double> >(*q,N,N,N);
|
||||
int64_t lwi = oneapi::mkl::lapack::getri_scratchpad_size<std::complex<double> >(*q,N,N);
|
||||
deviceVector<ComplexD> scratchf(lwf);
|
||||
deviceVector<ComplexD> scratchi(lwi);
|
||||
deviceVector<int64_t> ipiv(N);
|
||||
for(int i=0;i<batchCount;i++){
|
||||
oneapi::mkl::lapack::getrf(*q,N,N,(std::complex<double>*)Aptr_h[i],N,&ipiv[0],
|
||||
(std::complex<double>*)&scratchf[0],lwf);
|
||||
oneapi::mkl::lapack::getri(*q,N, (std::complex<double>*)Aptr_h[i],N,&ipiv[0],
|
||||
(std::complex<double>*)&scratchi[0],lwi);
|
||||
}
|
||||
q->wait();
|
||||
#endif
|
||||
#if !defined(GRID_SYCL) && !defined(GRID_CUDA) && !defined(GRID_HIP)
|
||||
thread_for (p, batchCount, {
|
||||
Eigen::Map<Eigen::MatrixXcd> eA(Amat[p],N,N);
|
||||
Eigen::PartialPivLU<Eigen::MatrixXcd> lu(eA);
|
||||
eA = lu.inverse();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,300 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: MomentumProject.h
|
||||
|
||||
Copyright (C) 2025
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
/*
|
||||
MultiMomProject
|
||||
|
||||
Import vectors -> nxyz x (ncomponent x nt)
|
||||
Import complex phases -> nmom x nxy
|
||||
|
||||
apply = via (possibly batched) GEMM
|
||||
*/
|
||||
template<class Field, class ComplexField>
|
||||
class MomentumProject
|
||||
{
|
||||
public:
|
||||
|
||||
typedef typename Field::scalar_type scalar;
|
||||
typedef typename Field::scalar_object scalar_object;
|
||||
|
||||
GridBase *grid;
|
||||
uint64_t nmom;
|
||||
uint64_t nxyz;
|
||||
uint64_t nt;
|
||||
uint64_t nbtw;
|
||||
uint64_t words;
|
||||
|
||||
deviceVector<scalar> BLAS_V; //
|
||||
deviceVector<scalar> BLAS_M; //
|
||||
deviceVector<scalar> BLAS_P; //
|
||||
|
||||
MomentumProject(){};
|
||||
~MomentumProject(){ Deallocate(); };
|
||||
|
||||
void Deallocate(void)
|
||||
{
|
||||
grid=nullptr;
|
||||
nmom=0;
|
||||
nxyz=0;
|
||||
nt=0;
|
||||
nbtw=0;
|
||||
words=0;
|
||||
BLAS_V.resize(0);
|
||||
BLAS_M.resize(0);
|
||||
BLAS_P.resize(0);
|
||||
}
|
||||
void Allocate(int _nmom,GridBase *_grid)
|
||||
{
|
||||
grid=_grid;
|
||||
Coordinate ldims = grid->LocalDimensions();
|
||||
|
||||
nmom=_nmom;
|
||||
nt = ldims[grid->Nd()-1];
|
||||
nxyz = grid->lSites()/nt;
|
||||
words = sizeof(scalar_object)/sizeof(scalar);
|
||||
nbtw = nt * words;
|
||||
|
||||
BLAS_V.resize (nxyz * nt * words );
|
||||
BLAS_M.resize (nmom * nxyz );
|
||||
BLAS_P.resize (nmom * nt * words );
|
||||
}
|
||||
void ImportMomenta(const std::vector <ComplexField> &momenta)
|
||||
{
|
||||
GRID_ASSERT(momenta.size()==nmom);
|
||||
// might as well just make the momenta here
|
||||
typedef typename Field::vector_object vobj;
|
||||
|
||||
int nd = grid->_ndimension;
|
||||
|
||||
uint64_t sz = BLAS_M.size();
|
||||
|
||||
GRID_ASSERT(momenta.size()==nmom)
|
||||
GRID_ASSERT(momenta[0].Grid()==grid);
|
||||
GRID_ASSERT(sz = nxyz * nmom);
|
||||
|
||||
Coordinate rdimensions = grid->_rdimensions;
|
||||
Coordinate ldims = grid->LocalDimensions();
|
||||
int64_t osites = grid->oSites();
|
||||
Coordinate simd = grid->_simd_layout;
|
||||
const int Nsimd = vobj::Nsimd();
|
||||
uint64_t lwords = words; // local variable for copy in to GPU
|
||||
int64_t Nxyz = nxyz;
|
||||
auto blasData_p = &BLAS_M[0];
|
||||
for(int m=0;m<momenta.size();m++){
|
||||
|
||||
autoView( Data , momenta[m], AcceleratorRead);
|
||||
auto Data_p = &Data[0];
|
||||
|
||||
accelerator_for(xyz,nxyz,1,{
|
||||
//////////////////////////////////////////
|
||||
// isite -- map lane within buffer to lane within lattice
|
||||
////////////////////////////////////////////
|
||||
Coordinate lcoor(nd,0);
|
||||
Lexicographic::CoorFromIndex(lcoor,xyz,ldims);
|
||||
|
||||
Coordinate icoor(nd);
|
||||
Coordinate ocoor(nd);
|
||||
for (int d = 0; d < nd; d++) {
|
||||
icoor[d] = lcoor[d]/rdimensions[d];
|
||||
ocoor[d] = lcoor[d]%rdimensions[d];
|
||||
}
|
||||
int64_t osite;
|
||||
int64_t isite;
|
||||
Lexicographic::IndexFromCoor(ocoor,osite,rdimensions);
|
||||
Lexicographic::IndexFromCoor(icoor,isite,simd);
|
||||
|
||||
// BLAS_M[nmom][slice_vol]
|
||||
// Fortran Column major BLAS layout is M_xyz,mom
|
||||
scalar data = extractLane(isite,Data[osite]);
|
||||
uint64_t idx = xyz+m*Nxyz;
|
||||
blasData_p[idx] = data;
|
||||
});
|
||||
}
|
||||
}
|
||||
void ImportVector(Field &vec)
|
||||
{
|
||||
typedef typename Field::vector_object vobj;
|
||||
|
||||
int nd = grid->_ndimension;
|
||||
|
||||
uint64_t sz = BLAS_V.size();
|
||||
|
||||
GRID_ASSERT(sz = nxyz * words * nt);
|
||||
|
||||
Coordinate rdimensions = grid->_rdimensions;
|
||||
Coordinate ldims= grid->LocalDimensions();
|
||||
int64_t osites = grid->oSites();
|
||||
Coordinate simd = grid->_simd_layout;
|
||||
const int Nsimd = vobj::Nsimd();
|
||||
uint64_t lwords= words; // local variable for copy in to GPU
|
||||
|
||||
auto blasData_p = &BLAS_V[0];
|
||||
autoView( Data , vec, AcceleratorRead);
|
||||
auto Data_p = &Data[0];
|
||||
|
||||
int64_t nwords = words;// for capture
|
||||
int64_t Nt = nt;// for capture
|
||||
|
||||
accelerator_for(sf,osites,Nsimd,{
|
||||
#ifdef GRID_SIMT
|
||||
{
|
||||
int lane=acceleratorSIMTlane(Nsimd); // buffer lane
|
||||
#else
|
||||
for(int lane=0;lane<Nsimd;lane++) {
|
||||
#endif
|
||||
//////////////////////////////////////////
|
||||
// isite -- map lane within buffer to lane within lattice
|
||||
////////////////////////////////////////////
|
||||
Coordinate lcoor(nd,0);
|
||||
Coordinate icoor(nd);
|
||||
Coordinate ocoor(nd);
|
||||
|
||||
Lexicographic::CoorFromIndex(icoor,lane,simd);
|
||||
Lexicographic::CoorFromIndex(ocoor,sf,rdimensions);
|
||||
|
||||
|
||||
int64_t l_xyz = 0;
|
||||
for (int d = 0; d < nd; d++) {
|
||||
lcoor[d] = rdimensions[d]*icoor[d] + ocoor[d];
|
||||
}
|
||||
uint64_t l_t = lcoor[nd-1];
|
||||
|
||||
Coordinate xyz_coor = lcoor;
|
||||
xyz_coor[nd-1] =0;
|
||||
Lexicographic::IndexFromCoor(xyz_coor,l_xyz,ldims);
|
||||
|
||||
|
||||
scalar_object data = extractLane(lane,Data[sf]);
|
||||
scalar *data_words = (scalar *) &data;
|
||||
for(int w = 0 ; w < nwords; w++) {
|
||||
// BLAS_V[slice_vol][nt][words]
|
||||
// Fortran Column major BLAS layout is V_(t,w)_xyz
|
||||
uint64_t idx = w+l_t*nwords + l_xyz * nwords * Nt;
|
||||
blasData_p[idx] = data_words[w];
|
||||
}
|
||||
#ifdef GRID_SIMT
|
||||
}
|
||||
#else
|
||||
}
|
||||
#endif
|
||||
});
|
||||
}
|
||||
void ExportMomentumProjection(std::vector<typename Field::scalar_object> &projection)
|
||||
{
|
||||
projection.resize(nmom*nt);
|
||||
acceleratorCopyFromDevice(&BLAS_P[0],(scalar *)&projection[0],BLAS_P.size()*sizeof(scalar));
|
||||
// Could decide on a layout late?
|
||||
}
|
||||
|
||||
// Row major layout "C" order:
|
||||
// BLAS_V[slice_vol][nt][words]
|
||||
// BLAS_M[nmom][slice_vol]
|
||||
// BLAS_P[nmom][nt][words]
|
||||
//
|
||||
// Fortran Column major BLAS layout is V_(w,t)_xyz
|
||||
// Fortran Column major BLAS layout is M_xyz,mom
|
||||
// Fortran Column major BLAS layout is P_(w,t),mom
|
||||
//
|
||||
// Projected
|
||||
//
|
||||
// P = (V * M)_(w,t),mom
|
||||
//
|
||||
void Project(Field &data,std::vector< typename Field::scalar_object > & projected_gdata)
|
||||
{
|
||||
double t_import=0;
|
||||
double t_export=0;
|
||||
double t_gemm =0;
|
||||
double t_allreduce=0;
|
||||
t_import-=usecond();
|
||||
this->ImportVector(data);
|
||||
|
||||
std::vector< typename Field::scalar_object > projected_planes;
|
||||
|
||||
deviceVector<scalar *> Vd(1);
|
||||
deviceVector<scalar *> Md(1);
|
||||
deviceVector<scalar *> Pd(1);
|
||||
|
||||
scalar * Vh = & BLAS_V[0];
|
||||
scalar * Mh = & BLAS_M[0];
|
||||
scalar * Ph = & BLAS_P[0];
|
||||
|
||||
acceleratorPut(Vd[0],Vh);
|
||||
acceleratorPut(Md[0],Mh);
|
||||
acceleratorPut(Pd[0],Ph);
|
||||
t_import+=usecond();
|
||||
|
||||
GridBLAS BLAS;
|
||||
|
||||
/////////////////////////////////////////
|
||||
// P_im = VMmx . Vxi
|
||||
/////////////////////////////////////////
|
||||
t_gemm-=usecond();
|
||||
BLAS.gemmBatched(GridBLAS_OP_N,GridBLAS_OP_N,
|
||||
words*nt,nmom,nxyz,
|
||||
scalar(1.0),
|
||||
Vd,
|
||||
Md,
|
||||
scalar(0.0), // wipe out result
|
||||
Pd);
|
||||
BLAS.synchronise();
|
||||
t_gemm+=usecond();
|
||||
|
||||
t_export-=usecond();
|
||||
ExportMomentumProjection(projected_planes); // resizes
|
||||
t_export+=usecond();
|
||||
|
||||
/////////////////////////////////
|
||||
// Reduce across MPI ranks
|
||||
/////////////////////////////////
|
||||
int nd = grid->Nd();
|
||||
int gt = grid->GlobalDimensions()[nd-1];
|
||||
int lt = grid->LocalDimensions()[nd-1];
|
||||
projected_gdata.resize(gt*nmom);
|
||||
for(int t=0;t<gt*nmom;t++){ // global Nt array with zeroes for stuff not on this node
|
||||
projected_gdata[t]=Zero();
|
||||
}
|
||||
for(int t=0;t<lt;t++){
|
||||
for(int m=0;m<nmom;m++){
|
||||
int st = grid->LocalStarts()[nd-1];
|
||||
projected_gdata[t+st + gt*m] = projected_planes[t+lt*m];
|
||||
}}
|
||||
t_allreduce-=usecond();
|
||||
grid->GlobalSumVector((scalar *)&projected_gdata[0],gt*nmom*words);
|
||||
t_allreduce+=usecond();
|
||||
|
||||
std::cout << GridLogPerformance<<" MomentumProject t_import "<<t_import<<"us"<<std::endl;
|
||||
std::cout << GridLogPerformance<<" MomentumProject t_export "<<t_export<<"us"<<std::endl;
|
||||
std::cout << GridLogPerformance<<" MomentumProject t_gemm "<<t_gemm<<"us"<<std::endl;
|
||||
std::cout << GridLogPerformance<<" MomentumProject t_reduce "<<t_allreduce<<"us"<<std::endl;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -69,8 +69,8 @@ public:
|
||||
DeflatedGuesser(const std::vector<Field> & _evec, const std::vector<RealD> & _eval, const unsigned int _N)
|
||||
: evec(_evec), eval(_eval), N(_N)
|
||||
{
|
||||
assert(evec.size()==eval.size());
|
||||
assert(N <= evec.size());
|
||||
GRID_ASSERT(evec.size()==eval.size());
|
||||
GRID_ASSERT(N <= evec.size());
|
||||
}
|
||||
|
||||
virtual void operator()(const Field &src,Field &guess) {
|
||||
@@ -141,11 +141,10 @@ public:
|
||||
}
|
||||
//postprocessing
|
||||
std::cout << GridLogMessage << "Start BlockPromote for loop" << std::endl;
|
||||
for (int j=0;j<Nsrc;j++)
|
||||
{
|
||||
std::cout << GridLogMessage << "BlockProject iter: " << j << std::endl;
|
||||
blockPromote(guess_coarse[j],guess[j],subspace);
|
||||
guess[j].Checkerboard() = src[j].Checkerboard();
|
||||
for (int j=0;j<Nsrc;j++) {
|
||||
std::cout << GridLogMessage << "BlockProject iter: " << j << std::endl;
|
||||
blockPromote(guess_coarse[j],guess[j],subspace);
|
||||
guess[j].Checkerboard() = src[j].Checkerboard();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,376 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: MultiRHSBlockCGLinalg.h
|
||||
|
||||
Copyright (C) 2024
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
|
||||
/* Need helper object for BLAS accelerated mrhs blockCG */
|
||||
template<class Field>
|
||||
class MultiRHSBlockCGLinalg
|
||||
{
|
||||
public:
|
||||
|
||||
typedef typename Field::scalar_type scalar;
|
||||
typedef typename Field::scalar_object scalar_object;
|
||||
typedef typename Field::vector_object vector_object;
|
||||
|
||||
deviceVector<scalar> BLAS_X; // nrhs x vol -- the sources
|
||||
deviceVector<scalar> BLAS_Y; // nrhs x vol -- the result
|
||||
deviceVector<scalar> BLAS_C; // nrhs x nrhs -- the coefficients
|
||||
deviceVector<scalar> BLAS_Cred; // nrhs x nrhs x oSites -- reduction buffer
|
||||
deviceVector<scalar *> Xdip;
|
||||
deviceVector<scalar *> Ydip;
|
||||
deviceVector<scalar *> Cdip;
|
||||
|
||||
MultiRHSBlockCGLinalg() {};
|
||||
~MultiRHSBlockCGLinalg(){ Deallocate(); };
|
||||
|
||||
void Deallocate(void)
|
||||
{
|
||||
Xdip.resize(0);
|
||||
Ydip.resize(0);
|
||||
Cdip.resize(0);
|
||||
BLAS_Cred.resize(0);
|
||||
BLAS_C.resize(0);
|
||||
BLAS_X.resize(0);
|
||||
BLAS_Y.resize(0);
|
||||
}
|
||||
void MaddMatrix(std::vector<Field> &AP, Eigen::MatrixXcd &m , const std::vector<Field> &X,const std::vector<Field> &Y,RealD scale=1.0)
|
||||
{
|
||||
std::vector<Field> Y_copy(AP.size(),AP[0].Grid());
|
||||
for(int r=0;r<AP.size();r++){
|
||||
Y_copy[r] = Y[r];
|
||||
}
|
||||
MulMatrix(AP,m,X);
|
||||
for(int r=0;r<AP.size();r++){
|
||||
AP[r] = scale*AP[r]+Y_copy[r];
|
||||
}
|
||||
}
|
||||
void MulMatrix(std::vector<Field> &Y, Eigen::MatrixXcd &m , const std::vector<Field> &X)
|
||||
{
|
||||
typedef typename Field::scalar_type scomplex;
|
||||
GridBase *grid;
|
||||
uint64_t vol;
|
||||
uint64_t words;
|
||||
|
||||
int nrhs = Y.size();
|
||||
grid = X[0].Grid();
|
||||
vol = grid->lSites();
|
||||
words = sizeof(scalar_object)/sizeof(scalar);
|
||||
int64_t vw = vol * words;
|
||||
|
||||
RealD t0 = usecond();
|
||||
BLAS_X.resize(nrhs * vw); // cost free if size doesn't change
|
||||
BLAS_Y.resize(nrhs * vw); // cost free if size doesn't change
|
||||
BLAS_C.resize(nrhs * nrhs);// cost free if size doesn't change
|
||||
RealD t1 = usecond();
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// Copy in the multi-rhs sources
|
||||
/////////////////////////////////////////////
|
||||
for(int r=0;r<nrhs;r++){
|
||||
int64_t offset = r*vw;
|
||||
autoView(x_v,X[r],AcceleratorRead);
|
||||
acceleratorCopyDeviceToDevice(&x_v[0],&BLAS_X[offset],sizeof(scalar_object)*vol);
|
||||
}
|
||||
|
||||
// Assumes Eigen storage contiguous
|
||||
acceleratorCopyToDevice(&m(0,0),&BLAS_C[0],BLAS_C.size()*sizeof(scalar));
|
||||
|
||||
/*
|
||||
* in Fortran column major notation (cuBlas order)
|
||||
*
|
||||
* Xxr = [X1(x)][..][Xn(x)]
|
||||
* Yxr = [Y1(x)][..][Ym(x)]
|
||||
* Y = X . C
|
||||
*/
|
||||
deviceVector<scalar *> Xd(1);
|
||||
deviceVector<scalar *> Yd(1);
|
||||
deviceVector<scalar *> Cd(1);
|
||||
|
||||
scalar * Xh = & BLAS_X[0];
|
||||
scalar * Yh = & BLAS_Y[0];
|
||||
scalar * Ch = & BLAS_C[0];
|
||||
|
||||
acceleratorPut(Xd[0],Xh);
|
||||
acceleratorPut(Yd[0],Yh);
|
||||
acceleratorPut(Cd[0],Ch);
|
||||
|
||||
RealD t2 = usecond();
|
||||
GridBLAS BLAS;
|
||||
/////////////////////////////////////////
|
||||
// Y = X*C (transpose?)
|
||||
/////////////////////////////////////////
|
||||
BLAS.gemmBatched(GridBLAS_OP_N,GridBLAS_OP_N,
|
||||
vw,nrhs,nrhs,
|
||||
scalar(1.0),
|
||||
Xd,
|
||||
Cd,
|
||||
scalar(0.0), // wipe out Y
|
||||
Yd);
|
||||
BLAS.synchronise();
|
||||
RealD t3 = usecond();
|
||||
|
||||
// Copy back Y = m X
|
||||
for(int r=0;r<nrhs;r++){
|
||||
int64_t offset = r*vw;
|
||||
autoView(y_v,Y[r],AcceleratorWrite);
|
||||
acceleratorCopyDeviceToDevice(&BLAS_Y[offset],&y_v[0],sizeof(scalar_object)*vol);
|
||||
}
|
||||
RealD t4 = usecond();
|
||||
std::cout <<GridLogPerformance << "MulMatrix alloc took "<< t1-t0<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "MulMatrix preamble took "<< t2-t1<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "MulMatrix blas took "<< t3-t2<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "MulMatrix copy took "<< t4-t3<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "MulMatrix total "<< t4-t0<<" us"<<std::endl;
|
||||
}
|
||||
|
||||
void InnerProductMatrix(Eigen::MatrixXcd &m , const std::vector<Field> &X, const std::vector<Field> &Y)
|
||||
{
|
||||
#if 0
|
||||
int nrhs;
|
||||
GridBase *grid;
|
||||
uint64_t vol;
|
||||
uint64_t words;
|
||||
|
||||
nrhs = X.size();
|
||||
GRID_ASSERT(X.size()==Y.size());
|
||||
conformable(X[0],Y[0]);
|
||||
|
||||
grid = X[0].Grid();
|
||||
vol = grid->lSites();
|
||||
words = sizeof(scalar_object)/sizeof(scalar);
|
||||
int64_t vw = vol * words;
|
||||
|
||||
RealD t0 = usecond();
|
||||
BLAS_X.resize(nrhs * vw); // cost free if size doesn't change
|
||||
BLAS_Y.resize(nrhs * vw); // cost free if size doesn't change
|
||||
BLAS_C.resize(nrhs * nrhs);// cost free if size doesn't change
|
||||
RealD t1 = usecond();
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// Copy in the multi-rhs sources
|
||||
/////////////////////////////////////////////
|
||||
for(int r=0;r<nrhs;r++){
|
||||
int64_t offset = r*vw;
|
||||
autoView(x_v,X[r],AcceleratorRead);
|
||||
acceleratorCopyDeviceToDevice(&x_v[0],&BLAS_X[offset],sizeof(scalar_object)*vol);
|
||||
autoView(y_v,Y[r],AcceleratorRead);
|
||||
acceleratorCopyDeviceToDevice(&y_v[0],&BLAS_Y[offset],sizeof(scalar_object)*vol);
|
||||
}
|
||||
RealD t2 = usecond();
|
||||
|
||||
/*
|
||||
* in Fortran column major notation (cuBlas order)
|
||||
*
|
||||
* Xxr = [X1(x)][..][Xn(x)]
|
||||
*
|
||||
* Yxr = [Y1(x)][..][Ym(x)]
|
||||
*
|
||||
* C_rs = X^dag Y
|
||||
*/
|
||||
deviceVector<scalar *> Xd(1);
|
||||
deviceVector<scalar *> Yd(1);
|
||||
deviceVector<scalar *> Cd(1);
|
||||
|
||||
scalar * Xh = & BLAS_X[0];
|
||||
scalar * Yh = & BLAS_Y[0];
|
||||
scalar * Ch = & BLAS_C[0];
|
||||
|
||||
acceleratorPut(Xd[0],Xh);
|
||||
acceleratorPut(Yd[0],Yh);
|
||||
acceleratorPut(Cd[0],Ch);
|
||||
|
||||
GridBLAS BLAS;
|
||||
|
||||
RealD t3 = usecond();
|
||||
/////////////////////////////////////////
|
||||
// C_rs = X^dag Y
|
||||
/////////////////////////////////////////
|
||||
BLAS.gemmBatched(GridBLAS_OP_C,GridBLAS_OP_N,
|
||||
nrhs,nrhs,vw,
|
||||
ComplexD(1.0),
|
||||
Xd,
|
||||
Yd,
|
||||
ComplexD(0.0), // wipe out C
|
||||
Cd);
|
||||
BLAS.synchronise();
|
||||
RealD t4 = usecond();
|
||||
|
||||
std::vector<scalar> HOST_C(BLAS_C.size()); // nrhs . nrhs -- the coefficients
|
||||
acceleratorCopyFromDevice(&BLAS_C[0],&HOST_C[0],BLAS_C.size()*sizeof(scalar));
|
||||
grid->GlobalSumVector(&HOST_C[0],nrhs*nrhs);
|
||||
|
||||
RealD t5 = usecond();
|
||||
for(int rr=0;rr<nrhs;rr++){
|
||||
for(int r=0;r<nrhs;r++){
|
||||
int off = r+nrhs*rr;
|
||||
m(r,rr)=HOST_C[off];
|
||||
}
|
||||
}
|
||||
RealD t6 = usecond();
|
||||
uint64_t M=nrhs;
|
||||
uint64_t N=nrhs;
|
||||
uint64_t K=vw;
|
||||
RealD bytes = 1.0*sizeof(ComplexD)*(M*N*2+N*K+M*K);
|
||||
RealD flops = 8.0*M*N*K;
|
||||
flops = flops/(t4-t3)/1.e3;
|
||||
bytes = bytes/(t4-t3)/1.e3;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix m,n,k "<< M<<","<<N<<","<<K<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix alloc t1 "<< t1-t0<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix cp t2 "<< t2-t1<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix setup t3 "<< t3-t2<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix blas t4 "<< t4-t3<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix blas "<< flops<<" GF/s"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix blas "<< bytes<<" GB/s"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix gsum t5 "<< t5-t4<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix cp t6 "<< t6-t5<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix took "<< t6-t0<<" us"<<std::endl;
|
||||
#else
|
||||
int nrhs;
|
||||
GridBase *grid;
|
||||
uint64_t vol;
|
||||
uint64_t words;
|
||||
|
||||
nrhs = X.size();
|
||||
GRID_ASSERT(X.size()==Y.size());
|
||||
conformable(X[0],Y[0]);
|
||||
|
||||
grid = X[0].Grid();
|
||||
int rd0 = grid->_rdimensions[0] * grid->_rdimensions[1];
|
||||
vol = grid->oSites()/rd0;
|
||||
words = rd0*sizeof(vector_object)/sizeof(scalar);
|
||||
int64_t vw = vol * words;
|
||||
GRID_ASSERT(vw == grid->lSites()*sizeof(scalar_object)/sizeof(scalar));
|
||||
|
||||
RealD t0 = usecond();
|
||||
BLAS_X.resize(nrhs * vw); // cost free if size doesn't change
|
||||
BLAS_Y.resize(nrhs * vw); // cost free if size doesn't change
|
||||
BLAS_Cred.resize(nrhs * nrhs * vol);// cost free if size doesn't change
|
||||
RealD t1 = usecond();
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// Copy in the multi-rhs sources -- layout batched BLAS ready
|
||||
/////////////////////////////////////////////
|
||||
for(int r=0;r<nrhs;r++){
|
||||
autoView(x_v,X[r],AcceleratorRead);
|
||||
autoView(y_v,Y[r],AcceleratorRead);
|
||||
scalar *from_x=(scalar *)&x_v[0];
|
||||
scalar *from_y=(scalar *)&y_v[0];
|
||||
scalar *BX = &BLAS_X[0];
|
||||
scalar *BY = &BLAS_Y[0];
|
||||
accelerator_for(ssw,vw,1,{
|
||||
uint64_t ss=ssw/words;
|
||||
uint64_t w=ssw%words;
|
||||
uint64_t offset = w+r*words+ss*nrhs*words; // [ss][rhs][words]
|
||||
BX[offset] = from_x[ssw];
|
||||
BY[offset] = from_y[ssw];
|
||||
});
|
||||
}
|
||||
RealD t2 = usecond();
|
||||
|
||||
/*
|
||||
* in Fortran column major notation (cuBlas order)
|
||||
*
|
||||
* Xxr = [X1(x)][..][Xn(x)]
|
||||
*
|
||||
* Yxr = [Y1(x)][..][Ym(x)]
|
||||
*
|
||||
* C_rs = X^dag Y
|
||||
*/
|
||||
Xdip.resize(vol);
|
||||
Ydip.resize(vol);
|
||||
Cdip.resize(vol);
|
||||
std::vector<scalar *> Xh(vol);
|
||||
std::vector<scalar *> Yh(vol);
|
||||
std::vector<scalar *> Ch(vol);
|
||||
for(uint64_t ss=0;ss<vol;ss++){
|
||||
|
||||
Xh[ss] = & BLAS_X[ss*nrhs*words];
|
||||
Yh[ss] = & BLAS_Y[ss*nrhs*words];
|
||||
Ch[ss] = & BLAS_Cred[ss*nrhs*nrhs];
|
||||
|
||||
}
|
||||
acceleratorCopyToDevice(&Xh[0],&Xdip[0],vol*sizeof(scalar *));
|
||||
acceleratorCopyToDevice(&Yh[0],&Ydip[0],vol*sizeof(scalar *));
|
||||
acceleratorCopyToDevice(&Ch[0],&Cdip[0],vol*sizeof(scalar *));
|
||||
|
||||
GridBLAS BLAS;
|
||||
|
||||
RealD t3 = usecond();
|
||||
/////////////////////////////////////////
|
||||
// C_rs = X^dag Y
|
||||
/////////////////////////////////////////
|
||||
BLAS.gemmBatched(GridBLAS_OP_C,GridBLAS_OP_N,
|
||||
nrhs,nrhs,words,
|
||||
ComplexD(1.0),
|
||||
Xdip,
|
||||
Ydip,
|
||||
ComplexD(0.0), // wipe out C
|
||||
Cdip);
|
||||
BLAS.synchronise();
|
||||
RealD t4 = usecond();
|
||||
|
||||
std::vector<scalar> HOST_C(BLAS_Cred.size()); // nrhs . nrhs -- the coefficients
|
||||
acceleratorCopyFromDevice(&BLAS_Cred[0],&HOST_C[0],BLAS_Cred.size()*sizeof(scalar));
|
||||
|
||||
RealD t5 = usecond();
|
||||
m = Eigen::MatrixXcd::Zero(nrhs,nrhs);
|
||||
for(int ss=0;ss<vol;ss++){
|
||||
Eigen::Map<Eigen::MatrixXcd> eC((std::complex<double> *)&HOST_C[ss*nrhs*nrhs],nrhs,nrhs);
|
||||
m = m + eC;
|
||||
}
|
||||
RealD t6l = usecond();
|
||||
grid->GlobalSumVector((scalar *) &m(0,0),nrhs*nrhs);
|
||||
RealD t6 = usecond();
|
||||
uint64_t M=nrhs;
|
||||
uint64_t N=nrhs;
|
||||
uint64_t K=vw;
|
||||
RealD xybytes = grid->lSites()*sizeof(scalar_object);
|
||||
RealD bytes = 1.0*sizeof(ComplexD)*(M*N*2+N*K+M*K);
|
||||
RealD flops = 8.0*M*N*K;
|
||||
flops = flops/(t4-t3)/1.e3;
|
||||
bytes = bytes/(t4-t3)/1.e3;
|
||||
xybytes = 4*xybytes/(t2-t1)/1.e3;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix m,n,k "<< M<<","<<N<<","<<K<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix alloc t1 "<< t1-t0<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix cp t2 "<< t2-t1<<" us "<<xybytes<<" GB/s"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix setup t3 "<< t3-t2<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix blas t4 "<< t4-t3<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix blas "<< flops<<" GF/s"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix blas "<< bytes<<" GB/s"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix cp t5 "<< t5-t4<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix lsum t6l "<< t6l-t5<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix gsum t6 "<< t6-t6l<<" us"<<std::endl;
|
||||
std::cout <<GridLogPerformance<< "InnerProductMatrix took "<< t6-t0<<" us"<<std::endl;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,300 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: MultiRHSDeflation.h
|
||||
|
||||
Copyright (C) 2023
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
|
||||
/* Need helper object for BLAS accelerated mrhs projection
|
||||
|
||||
i) MultiRHS Deflation
|
||||
|
||||
Import Evecs -> nev x vol x internal
|
||||
Import vector of Lattice objects -> nrhs x vol x internal
|
||||
=> Cij (nrhs x Nev) via GEMM.
|
||||
=> Guess (nrhs x vol x internal) = C x evecs (via GEMM)
|
||||
Export
|
||||
|
||||
|
||||
ii) MultiRHS block projection
|
||||
|
||||
Import basis -> nblock x nbasis x (block x internal)
|
||||
Import vector of fine lattice objects -> nblock x nrhs x (block x internal)
|
||||
|
||||
=> coarse_(nrhs x nbasis )^block = via batched GEMM
|
||||
|
||||
iii) Alternate interface:
|
||||
Import higher dim Lattice object-> vol x nrhs layout
|
||||
|
||||
*/
|
||||
template<class Field>
|
||||
class MultiRHSDeflation
|
||||
{
|
||||
public:
|
||||
|
||||
typedef typename Field::scalar_type scalar;
|
||||
typedef typename Field::scalar_object scalar_object;
|
||||
typedef typename Field::vector_object vobj;
|
||||
|
||||
int nev;
|
||||
std::vector<RealD> eval;
|
||||
GridBase *grid;
|
||||
uint64_t vol;
|
||||
uint64_t words;
|
||||
|
||||
deviceVector<scalar> BLAS_E; // nev x vol -- the eigenbasis (up to a 1/sqrt(lambda))
|
||||
deviceVector<scalar> BLAS_R; // nrhs x vol -- the sources
|
||||
deviceVector<scalar> BLAS_G; // nrhs x vol -- the guess
|
||||
deviceVector<scalar> BLAS_C; // nrhs x nev -- the coefficients
|
||||
|
||||
MultiRHSDeflation(){};
|
||||
~MultiRHSDeflation(){ Deallocate(); };
|
||||
|
||||
void Deallocate(void)
|
||||
{
|
||||
nev=0;
|
||||
grid=nullptr;
|
||||
vol=0;
|
||||
words=0;
|
||||
// deviceVector is a std::vector with a device allocator: resize(0) drops
|
||||
// the size and keeps the capacity, returning no device memory. Swapping
|
||||
// with an empty vector destroys the buffer.
|
||||
deviceVector<scalar>().swap(BLAS_E);
|
||||
deviceVector<scalar>().swap(BLAS_R);
|
||||
deviceVector<scalar>().swap(BLAS_C);
|
||||
deviceVector<scalar>().swap(BLAS_G);
|
||||
}
|
||||
// Resident (non-evictable) device memory held by this deflator.
|
||||
uint64_t DeviceBytes(void)
|
||||
{
|
||||
return (BLAS_E.capacity()+BLAS_R.capacity()+BLAS_C.capacity()+BLAS_G.capacity())*sizeof(scalar);
|
||||
}
|
||||
void Allocate(int _nev,GridBase *_grid)
|
||||
{
|
||||
nev=_nev;
|
||||
grid=_grid;
|
||||
vol = grid->lSites();
|
||||
words = sizeof(scalar_object)/sizeof(scalar);
|
||||
eval.resize(nev);
|
||||
BLAS_E.resize (vol * words * nev );
|
||||
std::cout << GridLogMessage << " Allocate for "<<nev<<" eigenvectors and volume "<<vol<<std::endl;
|
||||
}
|
||||
void ImportEigenVector(Field &evec,RealD &_eval, int ev)
|
||||
{
|
||||
// std::cout << " ev " <<ev<<" eval "<<_eval<< std::endl;
|
||||
GRID_ASSERT(ev<eval.size());
|
||||
eval[ev] = _eval;
|
||||
|
||||
int64_t offset = ev*vol*words;
|
||||
autoView(v,evec,AcceleratorRead);
|
||||
acceleratorCopyDeviceToDevice(&v[0],&BLAS_E[offset],sizeof(scalar_object)*vol);
|
||||
|
||||
}
|
||||
void ImportEigenBasis(std::vector<Field> &evec,std::vector<RealD> &_eval)
|
||||
{
|
||||
ImportEigenBasis(evec,_eval,0,evec.size());
|
||||
}
|
||||
// Could use to import a batch of eigenvectors
|
||||
void ImportEigenBasis(std::vector<Field> &evec,std::vector<RealD> &_eval, int _ev0, int _nev)
|
||||
{
|
||||
GRID_ASSERT(_ev0+_nev<=evec.size());
|
||||
|
||||
Allocate(_nev,evec[0].Grid());
|
||||
|
||||
// Imports a sub-batch of eigenvectors, _ev0, ..., _ev0+_nev-1
|
||||
for(int e=0;e<nev;e++){
|
||||
std::cout << "Importing eigenvector "<<e<<" evalue "<<_eval[_ev0+e]<<std::endl;
|
||||
ImportEigenVector(evec[_ev0+e],_eval[_ev0+e],e);
|
||||
}
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Sources as a vector of D-dimensional fields: each is one contiguous
|
||||
// column of BLAS_R already, so import/export are straight copies.
|
||||
// The eigenvectors may live on a D grid or on the Nrhs=1 D+1 grid of the
|
||||
// same local volume; the two are the same bytes, so only the site count
|
||||
// is checked, not grid identity.
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
void DeflateSources(std::vector<Field> &source,std::vector<Field> & guess)
|
||||
{
|
||||
int nrhs = source.size();
|
||||
GRID_ASSERT(source.size()==guess.size());
|
||||
GRID_ASSERT(grid->lSites() == guess[0].Grid()->lSites());
|
||||
conformable(guess[0],source[0]);
|
||||
|
||||
int64_t vw = vol * words;
|
||||
BLAS_R.resize(nrhs * vw); // cost free if size doesn't change
|
||||
BLAS_G.resize(nrhs * vw); // cost free if size doesn't change
|
||||
BLAS_C.resize(nev * nrhs);// cost free if size doesn't change
|
||||
|
||||
for(int r=0;r<nrhs;r++){
|
||||
int64_t offset = r*vw;
|
||||
autoView(v,source[r],AcceleratorRead);
|
||||
acceleratorCopyDeviceToDevice(&v[0],&BLAS_R[offset],sizeof(scalar_object)*vol);
|
||||
}
|
||||
DeflateBLAS(nrhs);
|
||||
for(int r=0;r<nrhs;r++){
|
||||
int64_t offset = r*vw;
|
||||
autoView(v,guess[r],AcceleratorWrite);
|
||||
acceleratorCopyDeviceToDevice(&BLAS_G[offset],&v[0],sizeof(scalar_object)*vol);
|
||||
}
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Sources as ONE D+1 dimensional multiRHS field, rhs innermost
|
||||
// (dimension 0, undistributed) on an UNVECTORISED coarse space, so the
|
||||
// site index is rhs + nrhs*x with x the D-dimensional site. The GEMM
|
||||
// wants x contiguous per rhs, so the import is a permutation, one fused
|
||||
// pass over the field; likewise the export. No slices.
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
void DeflateSources(const Field &source_mrhs, Field &guess_mrhs)
|
||||
{
|
||||
conformable(source_mrhs,guess_mrhs);
|
||||
GridBase *hi = source_mrhs.Grid();
|
||||
GRID_ASSERT(hi->_ndimension == grid->_ndimension+1);
|
||||
GRID_ASSERT(hi->_processors[0] == 1);
|
||||
GRID_ASSERT(hi->_simd_layout[0]== 1);
|
||||
GRID_ASSERT(vobj::Nsimd() == 1);
|
||||
int nrhs = hi->_fdimensions[0];
|
||||
GRID_ASSERT(hi->lSites() == nrhs*vol);
|
||||
|
||||
int64_t vw = vol * words;
|
||||
BLAS_R.resize(nrhs * vw);
|
||||
BLAS_G.resize(nrhs * vw);
|
||||
BLAS_C.resize(nev * nrhs);
|
||||
|
||||
ImportSourcesMrhs(source_mrhs,nrhs);
|
||||
DeflateBLAS(nrhs);
|
||||
ExportGuessMrhs(guess_mrhs,nrhs);
|
||||
}
|
||||
void ImportSourcesMrhs(const Field &source_mrhs,int nrhs)
|
||||
{
|
||||
const int64_t lwords = words, lnrhs = nrhs, vw = vol*words;
|
||||
autoView(v,source_mrhs,AcceleratorRead);
|
||||
auto vp = &v[0];
|
||||
scalar *R = &BLAS_R[0];
|
||||
accelerator_for(scr, lnrhs*vol, 1, {
|
||||
int64_t r = scr % lnrhs;
|
||||
int64_t x = scr / lnrhs;
|
||||
const scalar *s = (const scalar *)&vp[scr];
|
||||
for(int64_t w=0;w<lwords;w++) R[r*vw + x*lwords + w] = s[w];
|
||||
});
|
||||
}
|
||||
void ExportGuessMrhs(Field &guess_mrhs,int nrhs)
|
||||
{
|
||||
const int64_t lwords = words, lnrhs = nrhs, vw = vol*words;
|
||||
autoView(v,guess_mrhs,AcceleratorWrite);
|
||||
auto vp = &v[0];
|
||||
scalar *G = &BLAS_G[0];
|
||||
accelerator_for(scr, lnrhs*vol, 1, {
|
||||
int64_t r = scr % lnrhs;
|
||||
int64_t x = scr / lnrhs;
|
||||
scalar *g = (scalar *)&vp[scr];
|
||||
for(int64_t w=0;w<lwords;w++) g[w] = G[r*vw + x*lwords + w];
|
||||
});
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// BLAS_R (nrhs columns of vol*words) -> BLAS_G, through the imported
|
||||
// eigenbasis: C = E^dag R / lambda, G = E C.
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
void DeflateBLAS(int nrhs)
|
||||
{
|
||||
int64_t vw = vol * words;
|
||||
RealD t0 = usecond();
|
||||
|
||||
/*
|
||||
* in Fortran column major notation (cuBlas order)
|
||||
*
|
||||
* Exe = [e1(x)][..][en(x)]
|
||||
*
|
||||
* Rxr = [r1(x)][..][rm(x)]
|
||||
*
|
||||
* C_er = E^dag R
|
||||
* C_er = C_er / lambda_e
|
||||
* G_xr = Exe Cer
|
||||
*/
|
||||
deviceVector<scalar *> Ed(1);
|
||||
deviceVector<scalar *> Rd(1);
|
||||
deviceVector<scalar *> Cd(1);
|
||||
deviceVector<scalar *> Gd(1);
|
||||
|
||||
scalar * Eh = & BLAS_E[0];
|
||||
scalar * Rh = & BLAS_R[0];
|
||||
scalar * Ch = & BLAS_C[0];
|
||||
scalar * Gh = & BLAS_G[0];
|
||||
|
||||
acceleratorPut(Ed[0],Eh);
|
||||
acceleratorPut(Rd[0],Rh);
|
||||
acceleratorPut(Cd[0],Ch);
|
||||
acceleratorPut(Gd[0],Gh);
|
||||
|
||||
GridBLAS BLAS;
|
||||
|
||||
/////////////////////////////////////////
|
||||
// C_er = E^dag R
|
||||
/////////////////////////////////////////
|
||||
BLAS.gemmBatched(GridBLAS_OP_C,GridBLAS_OP_N,
|
||||
nev,nrhs,vw,
|
||||
scalar(1.0),
|
||||
Ed,
|
||||
Rd,
|
||||
scalar(0.0), // wipe out C
|
||||
Cd);
|
||||
BLAS.synchronise();
|
||||
|
||||
GRID_ASSERT(BLAS_C.size()==nev*nrhs);
|
||||
|
||||
std::vector<scalar> HOST_C(BLAS_C.size()); // nrhs . nev -- the coefficients
|
||||
acceleratorCopyFromDevice(&BLAS_C[0],&HOST_C[0],BLAS_C.size()*sizeof(scalar));
|
||||
grid->GlobalSumVector(&HOST_C[0],nev*nrhs);
|
||||
for(int e=0;e<nev;e++){
|
||||
scalar lam(1.0/eval[e]); // in the coarse scalar: fp32 coarse spaces too
|
||||
for(int r=0;r<nrhs;r++){
|
||||
int off = e+nev*r;
|
||||
HOST_C[off]=HOST_C[off] * lam;
|
||||
// std::cout << "C["<<e<<"]["<<r<<"] ="<<HOST_C[off]<< " eval[e] "<<eval[e] <<std::endl;
|
||||
}
|
||||
}
|
||||
acceleratorCopyToDevice(&HOST_C[0],&BLAS_C[0],BLAS_C.size()*sizeof(scalar));
|
||||
|
||||
|
||||
/////////////////////////////////////////
|
||||
// Guess G_xr = Exe Cer
|
||||
/////////////////////////////////////////
|
||||
BLAS.gemmBatched(GridBLAS_OP_N,GridBLAS_OP_N,
|
||||
vw,nrhs,nev,
|
||||
scalar(1.0),
|
||||
Ed, // x . nev
|
||||
Cd, // nev . nrhs
|
||||
scalar(0.0),
|
||||
Gd);
|
||||
BLAS.synchronise();
|
||||
RealD t1 = usecond();
|
||||
std::cout << GridLogMessage << "MultiRHSDeflation for "<<nrhs<<" sources with "<<nev<<" eigenvectors took " << (t1-t0)/1e3 <<" ms"<<std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -33,109 +33,134 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
|
||||
* Script A = SolverMatrix
|
||||
* Script P = Preconditioner
|
||||
*
|
||||
* Deflation methods considered
|
||||
* -- Solve P A x = P b [ like Luscher ]
|
||||
* DEF-1 M P A x = M P b [i.e. left precon]
|
||||
* DEF-2 P^T M A x = P^T M b
|
||||
* ADEF-1 Preconditioner = M P + Q [ Q + M + M A Q]
|
||||
* ADEF-2 Preconditioner = P^T M + Q
|
||||
* BNN Preconditioner = P^T M P + Q
|
||||
* BNN2 Preconditioner = M P + P^TM +Q - M P A M
|
||||
*
|
||||
* Implement ADEF-2
|
||||
*
|
||||
* Vstart = P^Tx + Qb
|
||||
* M1 = P^TM + Q
|
||||
* M2=M3=1
|
||||
* Vout = x
|
||||
*/
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
// abstract base
|
||||
template<class Field, class CoarseField>
|
||||
class TwoLevelFlexiblePcg : public LinearFunction<Field>
|
||||
|
||||
template<class Field>
|
||||
class TwoLevelCG : public LinearFunction<Field>
|
||||
{
|
||||
public:
|
||||
int verbose;
|
||||
RealD Tolerance;
|
||||
Integer MaxIterations;
|
||||
const int mmax = 5;
|
||||
GridBase *grid;
|
||||
GridBase *coarsegrid;
|
||||
|
||||
LinearOperatorBase<Field> *_Linop
|
||||
OperatorFunction<Field> *_Smoother,
|
||||
LinearFunction<CoarseField> *_CoarseSolver;
|
||||
// Fine operator, Smoother, CoarseSolver
|
||||
LinearOperatorBase<Field> &_FineLinop;
|
||||
LinearFunction<Field> &_Smoother;
|
||||
|
||||
GridStopWatch ProjectTimer;
|
||||
GridStopWatch PromoteTimer;
|
||||
GridStopWatch CoarseTimer;
|
||||
GridStopWatch SmoothTimer;
|
||||
GridStopWatch MatrixTimer;
|
||||
GridStopWatch M3Timer;
|
||||
GridStopWatch LinalgTimer;
|
||||
|
||||
int64_t M3Calls;
|
||||
int64_t SmoothCalls;
|
||||
int64_t MatrixCalls;
|
||||
int64_t ProjectCalls;
|
||||
int64_t CoarseCalls;
|
||||
int64_t PromoteCalls;
|
||||
|
||||
// Need somthing that knows how to get from Coarse to fine and back again
|
||||
|
||||
// more most opertor functions
|
||||
TwoLevelFlexiblePcg(RealD tol,
|
||||
Integer maxit,
|
||||
LinearOperatorBase<Field> *Linop,
|
||||
LinearOperatorBase<Field> *SmootherLinop,
|
||||
OperatorFunction<Field> *Smoother,
|
||||
OperatorFunction<CoarseField> CoarseLinop
|
||||
) :
|
||||
TwoLevelCG(RealD tol,
|
||||
Integer maxit,
|
||||
LinearOperatorBase<Field> &FineLinop,
|
||||
LinearFunction<Field> &Smoother,
|
||||
GridBase *fine) :
|
||||
Tolerance(tol),
|
||||
MaxIterations(maxit),
|
||||
_Linop(Linop),
|
||||
_PreconditionerLinop(PrecLinop),
|
||||
_Preconditioner(Preconditioner)
|
||||
{
|
||||
verbose=0;
|
||||
_FineLinop(FineLinop),
|
||||
_Smoother(Smoother)
|
||||
{
|
||||
grid = fine;
|
||||
};
|
||||
|
||||
// The Pcg routine is common to all, but the various matrices differ from derived
|
||||
// implementation to derived implmentation
|
||||
void operator() (const Field &src, Field &psi){
|
||||
void operator() (const Field &src, Field &psi){
|
||||
|
||||
psi.Checkerboard() = src.Checkerboard();
|
||||
grid = src.Grid();
|
||||
|
||||
|
||||
virtual void operator() (const Field &src, Field &x)
|
||||
{
|
||||
std::cout << GridLogMessage<<"HDCG: fPcg starting single RHS"<<std::endl;
|
||||
RealD f;
|
||||
RealD rtzp,rtz,a,d,b;
|
||||
RealD rptzp;
|
||||
RealD tn;
|
||||
RealD guess = norm2(psi);
|
||||
RealD ssq = norm2(src);
|
||||
RealD rsq = ssq*Tolerance*Tolerance;
|
||||
|
||||
|
||||
/////////////////////////////
|
||||
// Set up history vectors
|
||||
/////////////////////////////
|
||||
std::vector<Field> p (mmax,grid);
|
||||
int mmax = 5;
|
||||
std::cout << GridLogMessage<<"HDCG: fPcg allocating"<<std::endl;
|
||||
std::vector<Field> p(mmax,grid);
|
||||
std::vector<Field> mmp(mmax,grid);
|
||||
std::vector<RealD> pAp(mmax);
|
||||
|
||||
Field x (grid); x = psi;
|
||||
Field z (grid);
|
||||
Field z(grid);
|
||||
Field tmp(grid);
|
||||
Field r (grid);
|
||||
Field mu (grid);
|
||||
|
||||
Field mp (grid);
|
||||
Field r (grid);
|
||||
Field mu (grid);
|
||||
|
||||
std::cout << GridLogMessage<<"HDCG: fPcg allocated"<<std::endl;
|
||||
//Initial residual computation & set up
|
||||
RealD guess = norm2(x);
|
||||
std::cout << GridLogMessage<<"HDCG: fPcg guess nrm "<<guess<<std::endl;
|
||||
RealD src_nrm = norm2(src);
|
||||
std::cout << GridLogMessage<<"HDCG: fPcg src nrm "<<src_nrm<<std::endl;
|
||||
|
||||
if ( src_nrm == 0.0 ) {
|
||||
std::cout << GridLogMessage<<"HDCG: fPcg given trivial source norm "<<src_nrm<<std::endl;
|
||||
x=Zero();
|
||||
}
|
||||
RealD tn;
|
||||
|
||||
GridStopWatch HDCGTimer;
|
||||
ProjectTimer.Reset();
|
||||
PromoteTimer.Reset();
|
||||
CoarseTimer.Reset();
|
||||
SmoothTimer.Reset();
|
||||
MatrixTimer.Reset();
|
||||
M3Timer.Reset();
|
||||
LinalgTimer.Reset();
|
||||
M3Calls = SmoothCalls = MatrixCalls = ProjectCalls = CoarseCalls = PromoteCalls = 0;
|
||||
HDCGTimer.Start();
|
||||
//////////////////////////
|
||||
// x0 = Vstart -- possibly modify guess
|
||||
//////////////////////////
|
||||
x=src;
|
||||
Vstart(x,src);
|
||||
|
||||
// r0 = b -A x0
|
||||
HermOp(x,mmp); // Shouldn't this be something else?
|
||||
_FineLinop.HermOp(x,mmp[0]);
|
||||
axpy (r, -1.0,mmp[0], src); // Recomputes r=src-Ax0
|
||||
{
|
||||
double n1 = norm2(x);
|
||||
double n2 = norm2(mmp[0]);
|
||||
double n3 = norm2(r);
|
||||
std::cout<<GridLogMessage<<"x,vstart,r = "<<n1<<" "<<n2<<" "<<n3<<std::endl;
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// Compute z = M1 x
|
||||
//////////////////////////////////
|
||||
M1(r,z,tmp,mp,SmootherMirs);
|
||||
PcgM1(r,z);
|
||||
rtzp =real(innerProduct(r,z));
|
||||
|
||||
|
||||
///////////////////////////////////////
|
||||
// Solve for Mss mu = P A z and set p = z-mu
|
||||
// Def2: p = 1 - Q Az = Pright z
|
||||
// Def2 p = 1 - Q Az = Pright z
|
||||
// Other algos M2 is trivial
|
||||
///////////////////////////////////////
|
||||
M2(z,p[0]);
|
||||
PcgM2(z,p[0]);
|
||||
|
||||
RealD ssq = norm2(src);
|
||||
RealD rsq = ssq*Tolerance*Tolerance;
|
||||
|
||||
std::cout << GridLogMessage<<"HDCG: k=0 residual "<<rtzp<<" rsq "<<rsq<<"\n";
|
||||
|
||||
Field pp(grid);
|
||||
|
||||
for (int k=0;k<=MaxIterations;k++){
|
||||
|
||||
@@ -143,31 +168,53 @@ class TwoLevelFlexiblePcg : public LinearFunction<Field>
|
||||
int peri_kp = (k+1) % mmax;
|
||||
|
||||
rtz=rtzp;
|
||||
d= M3(p[peri_k],mp,mmp[peri_k],tmp);
|
||||
M3Timer.Start();
|
||||
d= PcgM3(p[peri_k],mmp[peri_k]);
|
||||
M3Timer.Stop();
|
||||
M3Calls++;
|
||||
a = rtz/d;
|
||||
|
||||
|
||||
// Memorise this
|
||||
pAp[peri_k] = d;
|
||||
|
||||
LinalgTimer.Start();
|
||||
axpy(x,a,p[peri_k],x);
|
||||
RealD rn = axpy_norm(r,-a,mmp[peri_k],r);
|
||||
LinalgTimer.Stop();
|
||||
|
||||
// Compute z = M x
|
||||
M1(r,z,tmp,mp);
|
||||
PcgM1(r,z);
|
||||
|
||||
{
|
||||
RealD n1,n2;
|
||||
n1=norm2(r);
|
||||
n2=norm2(z);
|
||||
std::cout << GridLogMessage<<"HDCG::fPcg iteration "<<k<<" : vector r,z "<<n1<<" "<<n2<<"\n";
|
||||
}
|
||||
LinalgTimer.Start();
|
||||
rtzp =real(innerProduct(r,z));
|
||||
LinalgTimer.Stop();
|
||||
std::cout << GridLogMessage<<"HDCG::fPcg iteration "<<k<<" : inner rtzp "<<rtzp<<"\n";
|
||||
|
||||
M2(z,mu); // ADEF-2 this is identity. Axpy possible to eliminate
|
||||
// PcgM2(z,p[0]);
|
||||
PcgM2(z,mu); // ADEF-2 this is identity. Axpy possible to eliminate
|
||||
|
||||
p[peri_kp]=p[peri_k];
|
||||
p[peri_kp]=mu;
|
||||
|
||||
// Standard search direction p -> z + b p ; b =
|
||||
// Standard search direction p -> z + b p
|
||||
b = (rtzp)/rtz;
|
||||
|
||||
|
||||
int northog;
|
||||
// k=zero <=> peri_kp=1; northog = 1
|
||||
// k=1 <=> peri_kp=2; northog = 2
|
||||
// ... ... ...
|
||||
// k=mmax-2<=> peri_kp=mmax-1; northog = mmax-1
|
||||
// k=mmax-1<=> peri_kp=0; northog = 1
|
||||
|
||||
// northog = (peri_kp==0)?1:peri_kp; // This is the fCG(mmax) algorithm
|
||||
northog = (k>mmax-1)?(mmax-1):k; // This is the fCG-Tr(mmax-1) algorithm
|
||||
|
||||
std::cout<<GridLogMessage<<"HDCG::fPcg iteration "<<k<<" : orthogonalising to last "<<northog<<" vectors\n";
|
||||
for(int back=0; back < northog; back++){
|
||||
int peri_back = (k-back)%mmax;
|
||||
RealD pbApk= real(innerProduct(mmp[peri_back],p[peri_kp]));
|
||||
@@ -176,222 +223,320 @@ class TwoLevelFlexiblePcg : public LinearFunction<Field>
|
||||
}
|
||||
|
||||
RealD rrn=sqrt(rn/ssq);
|
||||
std::cout<<GridLogMessage<<"TwoLevelfPcg: k= "<<k<<" residual = "<<rrn<<std::endl;
|
||||
RealD rtn=sqrt(rtz/ssq);
|
||||
RealD rtnp=sqrt(rtzp/ssq);
|
||||
|
||||
std::cout<<GridLogMessage<<"HDCG: fPcg k= "<<k<<" residual = "<<rrn<<"\n";
|
||||
|
||||
// Stopping condition
|
||||
if ( rn <= rsq ) {
|
||||
|
||||
HermOp(x,mmp); // Shouldn't this be something else?
|
||||
HDCGTimer.Stop();
|
||||
std::cout<<GridLogMessage<<"HDCG: fPcg converged in "<<k<<" iterations and "<<HDCGTimer.Elapsed()<<std::endl;
|
||||
std::cout<<GridLogMessage<<"HDCG: fPcg breakdown"<<std::endl;
|
||||
auto mspc = [](GridStopWatch &sw, int64_t n) -> double {
|
||||
return (n > 0) ? sw.useconds() * 1e-3 / n : 0.0;
|
||||
};
|
||||
std::cout<<GridLogMessage<<"HDCG: fPcg M3 (fine MVM) "<<M3Timer.Elapsed()
|
||||
<<" "<<M3Calls<<" calls "<<mspc(M3Timer,M3Calls)<<" ms/call"<<std::endl;
|
||||
std::cout<<GridLogMessage<<"HDCG: fPcg linalg "<<LinalgTimer.Elapsed()<<std::endl;
|
||||
std::cout<<GridLogMessage<<"HDCG: fPcg smoother "<<SmoothTimer.Elapsed()
|
||||
<<" "<<SmoothCalls<<" calls "<<mspc(SmoothTimer,SmoothCalls)<<" ms/call"<<std::endl;
|
||||
std::cout<<GridLogMessage<<"HDCG: fPcg matrix (in M1) "<<MatrixTimer.Elapsed()
|
||||
<<" "<<MatrixCalls<<" calls "<<mspc(MatrixTimer,MatrixCalls)<<" ms/call"<<std::endl;
|
||||
std::cout<<GridLogMessage<<"HDCG: fPcg project "<<ProjectTimer.Elapsed()
|
||||
<<" "<<ProjectCalls<<" calls "<<mspc(ProjectTimer,ProjectCalls)<<" ms/call"<<std::endl;
|
||||
std::cout<<GridLogMessage<<"HDCG: fPcg coarse "<<CoarseTimer.Elapsed()
|
||||
<<" "<<CoarseCalls<<" calls "<<mspc(CoarseTimer,CoarseCalls)<<" ms/call"<<std::endl;
|
||||
std::cout<<GridLogMessage<<"HDCG: fPcg promote "<<PromoteTimer.Elapsed()
|
||||
<<" "<<PromoteCalls<<" calls "<<mspc(PromoteTimer,PromoteCalls)<<" ms/call"<<std::endl;
|
||||
|
||||
_FineLinop.HermOp(x,mmp[0]);
|
||||
axpy(tmp,-1.0,src,mmp[0]);
|
||||
|
||||
RealD psinorm = sqrt(norm2(x));
|
||||
RealD srcnorm = sqrt(norm2(src));
|
||||
RealD tmpnorm = sqrt(norm2(tmp));
|
||||
RealD true_residual = tmpnorm/srcnorm;
|
||||
std::cout<<GridLogMessage<<"TwoLevelfPcg: true residual is "<<true_residual<<std::endl;
|
||||
std::cout<<GridLogMessage<<"TwoLevelfPcg: target residual was"<<Tolerance<<std::endl;
|
||||
return k;
|
||||
RealD mmpnorm = sqrt(norm2(mmp[0]));
|
||||
RealD xnorm = sqrt(norm2(x));
|
||||
RealD srcnorm = sqrt(norm2(src));
|
||||
RealD tmpnorm = sqrt(norm2(tmp));
|
||||
RealD true_residual = tmpnorm/srcnorm;
|
||||
std::cout<<GridLogMessage
|
||||
<<"HDCG: true residual is "<<true_residual
|
||||
<<" solution "<<xnorm
|
||||
<<" source "<<srcnorm
|
||||
<<" mmp "<<mmpnorm
|
||||
<<std::endl;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
// Non-convergence
|
||||
assert(0);
|
||||
HDCGTimer.Stop();
|
||||
std::cout<<GridLogMessage<<"HDCG: not converged "<<HDCGTimer.Elapsed()<<std::endl;
|
||||
RealD xnorm = sqrt(norm2(x));
|
||||
RealD srcnorm = sqrt(norm2(src));
|
||||
std::cout<<GridLogMessage<<"HDCG: non-converged solution "<<xnorm<<" source "<<srcnorm<<std::endl;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
virtual void M(Field & in,Field & out,Field & tmp) {
|
||||
|
||||
}
|
||||
virtual void operator() (std::vector<Field> &src, std::vector<Field> &x)
|
||||
{
|
||||
std::cout << GridLogMessage<<"HDCG: mrhs fPcg starting"<<std::endl;
|
||||
src[0].Grid()->Barrier();
|
||||
int nrhs = src.size();
|
||||
std::vector<RealD> f(nrhs);
|
||||
std::vector<RealD> rtzp(nrhs);
|
||||
std::vector<RealD> rtz(nrhs);
|
||||
std::vector<RealD> a(nrhs);
|
||||
std::vector<RealD> d(nrhs);
|
||||
std::vector<RealD> b(nrhs);
|
||||
std::vector<RealD> rptzp(nrhs);
|
||||
/////////////////////////////
|
||||
// Set up history vectors
|
||||
/////////////////////////////
|
||||
int mmax = 3;
|
||||
std::cout << GridLogMessage<<"HDCG: fPcg allocating"<<std::endl;
|
||||
src[0].Grid()->Barrier();
|
||||
std::vector<std::vector<Field> > p(nrhs); for(int r=0;r<nrhs;r++) p[r].resize(mmax,grid);
|
||||
std::cout << GridLogMessage<<"HDCG: fPcg allocated p"<<std::endl;
|
||||
src[0].Grid()->Barrier();
|
||||
std::vector<std::vector<Field> > mmp(nrhs); for(int r=0;r<nrhs;r++) mmp[r].resize(mmax,grid);
|
||||
std::cout << GridLogMessage<<"HDCG: fPcg allocated mmp"<<std::endl;
|
||||
src[0].Grid()->Barrier();
|
||||
std::vector<std::vector<RealD> > pAp(nrhs); for(int r=0;r<nrhs;r++) pAp[r].resize(mmax);
|
||||
std::cout << GridLogMessage<<"HDCG: fPcg allocated pAp"<<std::endl;
|
||||
src[0].Grid()->Barrier();
|
||||
std::vector<Field> z(nrhs,grid);
|
||||
std::vector<Field> mp (nrhs,grid);
|
||||
std::vector<Field> r (nrhs,grid);
|
||||
std::vector<Field> mu (nrhs,grid);
|
||||
std::cout << GridLogMessage<<"HDCG: fPcg allocated z,mp,r,mu"<<std::endl;
|
||||
src[0].Grid()->Barrier();
|
||||
|
||||
virtual void M1(Field & in, Field & out) {// the smoother
|
||||
//Initial residual computation & set up
|
||||
std::vector<RealD> src_nrm(nrhs);
|
||||
for(int rhs=0;rhs<nrhs;rhs++) {
|
||||
src_nrm[rhs]=norm2(src[rhs]);
|
||||
GRID_ASSERT(src_nrm[rhs]!=0.0);
|
||||
}
|
||||
std::vector<RealD> tn(nrhs);
|
||||
|
||||
// [PTM+Q] in = [1 - Q A] M in + Q in = Min + Q [ in -A Min]
|
||||
Field tmp(grid);
|
||||
Field Min(grid);
|
||||
GridStopWatch HDCGTimer;
|
||||
HDCGTimer.Start();
|
||||
//////////////////////////
|
||||
// x0 = Vstart -- possibly modify guess
|
||||
//////////////////////////
|
||||
Vstart(x,src);
|
||||
|
||||
PcgM(in,Min); // Smoother call
|
||||
for(int rhs=0;rhs<nrhs;rhs++){
|
||||
// r0 = b -A x0
|
||||
_FineLinop.HermOp(x[rhs],mmp[rhs][0]);
|
||||
axpy (r[rhs], -1.0,mmp[rhs][0], src[rhs]); // Recomputes r=src-Ax0
|
||||
}
|
||||
|
||||
HermOp(Min,out);
|
||||
axpy(tmp,-1.0,out,in); // tmp = in - A Min
|
||||
//////////////////////////////////
|
||||
// Compute z = M1 x
|
||||
//////////////////////////////////
|
||||
// This needs a multiRHS version for acceleration
|
||||
PcgM1(r,z);
|
||||
|
||||
ProjectToSubspace(tmp,PleftProj);
|
||||
ApplyInverse(PleftProj,PleftMss_proj); // Ass^{-1} [in - A Min]_s
|
||||
PromoteFromSubspace(PleftMss_proj,tmp);// tmp = Q[in - A Min]
|
||||
axpy(out,1.0,Min,tmp); // Min+tmp
|
||||
}
|
||||
std::vector<RealD> ssq(nrhs);
|
||||
std::vector<RealD> rsq(nrhs);
|
||||
std::vector<Field> pp(nrhs,grid);
|
||||
|
||||
virtual void M2(const Field & in, Field & out) {
|
||||
out=in;
|
||||
// Must override for Def2 only
|
||||
// case PcgDef2:
|
||||
// Pright(in,out);
|
||||
// break;
|
||||
}
|
||||
for(int rhs=0;rhs<nrhs;rhs++){
|
||||
rtzp[rhs] =real(innerProduct(r[rhs],z[rhs]));
|
||||
p[rhs][0]=z[rhs];
|
||||
ssq[rhs]=norm2(src[rhs]);
|
||||
rsq[rhs]= ssq[rhs]*Tolerance*Tolerance;
|
||||
std::cout << GridLogMessage<<"mrhs HDCG: "<<rhs<<" k=0 residual "<<rtzp[rhs]<<" rsq "<<rsq[rhs]<<"\n";
|
||||
}
|
||||
|
||||
virtual RealD M3(const Field & p, Field & mmp){
|
||||
double d,dd;
|
||||
HermOpAndNorm(p,mmp,d,dd);
|
||||
return dd;
|
||||
// Must override for Def1 only
|
||||
// case PcgDef1:
|
||||
// d=linop_d->Mprec(p,mmp,tmp,0,1);// Dag no
|
||||
// linop_d->Mprec(mmp,mp,tmp,1);// Dag yes
|
||||
// Pleft(mp,mmp);
|
||||
// d=real(linop_d->inner(p,mmp));
|
||||
}
|
||||
|
||||
virtual void VstartDef2(Field & xconst Field & src){
|
||||
//case PcgDef2:
|
||||
//case PcgAdef2:
|
||||
//case PcgAdef2f:
|
||||
//case PcgV11f:
|
||||
///////////////////////////////////
|
||||
// Choose x_0 such that
|
||||
// x_0 = guess + (A_ss^inv) r_s = guess + Ass_inv [src -Aguess]
|
||||
// = [1 - Ass_inv A] Guess + Assinv src
|
||||
// = P^T guess + Assinv src
|
||||
// = Vstart [Tang notation]
|
||||
// This gives:
|
||||
// W^T (src - A x_0) = src_s - A guess_s - r_s
|
||||
// = src_s - (A guess)_s - src_s + (A guess)_s
|
||||
// = 0
|
||||
///////////////////////////////////
|
||||
Field r(grid);
|
||||
Field mmp(grid);
|
||||
std::vector<RealD> rn(nrhs);
|
||||
for (int k=0;k<=MaxIterations;k++){
|
||||
|
||||
HermOp(x,mmp);
|
||||
axpy (r, -1.0, mmp, src); // r_{-1} = src - A x
|
||||
ProjectToSubspace(r,PleftProj);
|
||||
ApplyInverseCG(PleftProj,PleftMss_proj); // Ass^{-1} r_s
|
||||
PromoteFromSubspace(PleftMss_proj,mmp);
|
||||
x=x+mmp;
|
||||
int peri_k = k % mmax;
|
||||
int peri_kp = (k+1) % mmax;
|
||||
|
||||
for(int rhs=0;rhs<nrhs;rhs++){
|
||||
rtz[rhs]=rtzp[rhs];
|
||||
d[rhs]= PcgM3(p[rhs][peri_k],mmp[rhs][peri_k]);
|
||||
a[rhs] = rtz[rhs]/d[rhs];
|
||||
|
||||
// Memorise this
|
||||
pAp[rhs][peri_k] = d[rhs];
|
||||
|
||||
axpy(x[rhs],a[rhs],p[rhs][peri_k],x[rhs]);
|
||||
rn[rhs] = axpy_norm(r[rhs],-a[rhs],mmp[rhs][peri_k],r[rhs]);
|
||||
}
|
||||
|
||||
// Compute z = M x (for *all* RHS)
|
||||
PcgM1(r,z);
|
||||
std::cout << GridLogMessage<<"HDCG::fPcg M1 complete"<<std::endl;
|
||||
grid->Barrier();
|
||||
|
||||
RealD max_rn=0.0;
|
||||
for(int rhs=0;rhs<nrhs;rhs++){
|
||||
|
||||
rtzp[rhs] =real(innerProduct(r[rhs],z[rhs]));
|
||||
|
||||
std::cout << GridLogMessage<<"HDCG::fPcg rhs"<<rhs<<" iteration "<<k<<" : inner rtzp "<<rtzp[rhs]<<"\n";
|
||||
|
||||
mu[rhs]=z[rhs];
|
||||
|
||||
p[rhs][peri_kp]=mu[rhs];
|
||||
|
||||
// Standard search direction p == z + b p
|
||||
b[rhs] = (rtzp[rhs])/rtz[rhs];
|
||||
|
||||
int northog = (k>mmax-1)?(mmax-1):k; // This is the fCG-Tr(mmax-1) algorithm
|
||||
std::cout<<GridLogMessage<<"HDCG::fPcg iteration "<<k<<" : orthogonalising to last "<<northog<<" vectors\n";
|
||||
for(int back=0; back < northog; back++){
|
||||
int peri_back = (k-back)%mmax;
|
||||
RealD pbApk= real(innerProduct(mmp[rhs][peri_back],p[rhs][peri_kp]));
|
||||
RealD beta = -pbApk/pAp[rhs][peri_back];
|
||||
axpy(p[rhs][peri_kp],beta,p[rhs][peri_back],p[rhs][peri_kp]);
|
||||
}
|
||||
|
||||
RealD rrn=sqrt(rn[rhs]/ssq[rhs]);
|
||||
RealD rtn=sqrt(rtz[rhs]/ssq[rhs]);
|
||||
RealD rtnp=sqrt(rtzp[rhs]/ssq[rhs]);
|
||||
|
||||
std::cout<<GridLogMessage<<"HDCG: rhs "<<rhs<<"fPcg k= "<<k<<" residual = "<<rrn<<"\n";
|
||||
if ( rrn > max_rn ) max_rn = rrn;
|
||||
}
|
||||
|
||||
// Stopping condition based on worst case
|
||||
if ( max_rn <= Tolerance ) {
|
||||
|
||||
HDCGTimer.Stop();
|
||||
std::cout<<GridLogMessage<<"HDCG: mrhs fPcg converged in "<<k<<" iterations and "<<HDCGTimer.Elapsed()<<std::endl;;
|
||||
|
||||
for(int rhs=0;rhs<nrhs;rhs++){
|
||||
_FineLinop.HermOp(x[rhs],mmp[rhs][0]);
|
||||
Field tmp(grid);
|
||||
axpy(tmp,-1.0,src[rhs],mmp[rhs][0]);
|
||||
|
||||
RealD mmpnorm = sqrt(norm2(mmp[rhs][0]));
|
||||
RealD xnorm = sqrt(norm2(x[rhs]));
|
||||
RealD srcnorm = sqrt(norm2(src[rhs]));
|
||||
RealD tmpnorm = sqrt(norm2(tmp));
|
||||
RealD true_residual = tmpnorm/srcnorm;
|
||||
std::cout<<GridLogMessage
|
||||
<<"HDCG: true residual ["<<rhs<<"] is "<<true_residual
|
||||
<<" solution "<<xnorm
|
||||
<<" source "<<srcnorm
|
||||
<<" mmp "<<mmpnorm
|
||||
<<std::endl;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
HDCGTimer.Stop();
|
||||
std::cout<<GridLogMessage<<"HDCG: not converged "<<HDCGTimer.Elapsed()<<std::endl;
|
||||
for(int rhs=0;rhs<nrhs;rhs++){
|
||||
RealD xnorm = sqrt(norm2(x[rhs]));
|
||||
RealD srcnorm = sqrt(norm2(src[rhs]));
|
||||
std::cout<<GridLogMessage<<"HDCG: non-converged solution "<<xnorm<<" source "<<srcnorm<<std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
|
||||
virtual void PcgM1(std::vector<Field> & in,std::vector<Field> & out)
|
||||
{
|
||||
std::cout << "PcgM1 default (cheat) mrhs version"<<std::endl;
|
||||
for(int rhs=0;rhs<in.size();rhs++){
|
||||
this->PcgM1(in[rhs],out[rhs]);
|
||||
}
|
||||
}
|
||||
virtual void PcgM1(Field & in, Field & out) =0;
|
||||
virtual void Vstart(std::vector<Field> & x,std::vector<Field> & src)
|
||||
{
|
||||
std::cout << "Vstart default (cheat) mrhs version"<<std::endl;
|
||||
for(int rhs=0;rhs<x.size();rhs++){
|
||||
this->Vstart(x[rhs],src[rhs]);
|
||||
}
|
||||
}
|
||||
virtual void Vstart(Field & x,const Field & src)=0;
|
||||
|
||||
virtual void PcgM2(const Field & in, Field & out) {
|
||||
out=in;
|
||||
}
|
||||
|
||||
virtual RealD PcgM3(const Field & p, Field & mmp){
|
||||
RealD dd;
|
||||
_FineLinop.HermOp(p,mmp);
|
||||
ComplexD dot = innerProduct(p,mmp);
|
||||
dd=real(dot);
|
||||
return dd;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Only Def1 has non-trivial Vout.
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
template<class Field>
|
||||
class TwoLevelADEF1defl : public TwoLevelCG<Field>
|
||||
{
|
||||
public:
|
||||
const std::vector<Field> &evec;
|
||||
const std::vector<RealD> &eval;
|
||||
|
||||
TwoLevelADEF1defl(RealD tol,
|
||||
Integer maxit,
|
||||
LinearOperatorBase<Field> &FineLinop,
|
||||
LinearFunction<Field> &Smoother,
|
||||
std::vector<Field> &_evec,
|
||||
std::vector<RealD> &_eval) :
|
||||
TwoLevelCG<Field>(tol,maxit,FineLinop,Smoother,_evec[0].Grid()),
|
||||
evec(_evec),
|
||||
eval(_eval)
|
||||
{};
|
||||
|
||||
// Can just inherit existing M2
|
||||
// Can just inherit existing M3
|
||||
|
||||
// Simple vstart - do nothing
|
||||
virtual void Vstart(Field & x,const Field & src){
|
||||
return;
|
||||
x=src; // Could apply Q
|
||||
};
|
||||
|
||||
// Override PcgM1
|
||||
virtual void PcgM1(Field & in, Field & out)
|
||||
{
|
||||
GRID_TRACE("EvecPreconditioner ");
|
||||
int N=evec.size();
|
||||
Field Pin(this->grid);
|
||||
Field Qin(this->grid);
|
||||
|
||||
//MP + Q = M(1-AQ) + Q = M
|
||||
// // If we are eigenvector deflating in coarse space
|
||||
// // Q = Sum_i |phi_i> 1/lambda_i <phi_i|
|
||||
// // A Q = Sum_i |phi_i> <phi_i|
|
||||
// // M(1-AQ) = M(1-proj) + Q
|
||||
Qin.Checkerboard()=in.Checkerboard();
|
||||
Qin = Zero();
|
||||
Pin = in;
|
||||
for (int i=0;i<N;i++) {
|
||||
const Field& tmp = evec[i];
|
||||
auto ip = TensorRemove(innerProduct(tmp,in));
|
||||
axpy(Qin, ip / eval[i],tmp,Qin);
|
||||
axpy(Pin, -ip ,tmp,Pin);
|
||||
}
|
||||
|
||||
this->_Smoother(Pin,out);
|
||||
|
||||
out = out + Qin;
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Only Def1 has non-trivial Vout. Override in Def1
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
virtual void Vout (Field & in, Field & out,Field & src){
|
||||
out = in;
|
||||
//case PcgDef1:
|
||||
// //Qb + PT x
|
||||
// ProjectToSubspace(src,PleftProj);
|
||||
// ApplyInverse(PleftProj,PleftMss_proj); // Ass^{-1} r_s
|
||||
// PromoteFromSubspace(PleftMss_proj,tmp);
|
||||
//
|
||||
// Pright(in,out);
|
||||
//
|
||||
// linop_d->axpy(out,tmp,out,1.0);
|
||||
// break;
|
||||
}
|
||||
NAMESPACE_END(Grid);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Pright and Pleft are common to all implementations
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
virtual void Pright(Field & in,Field & out){
|
||||
// P_R = [ 1 0 ]
|
||||
// [ -Mss^-1 Msb 0 ]
|
||||
Field in_sbar(grid);
|
||||
|
||||
ProjectToSubspace(in,PleftProj);
|
||||
PromoteFromSubspace(PleftProj,out);
|
||||
axpy(in_sbar,-1.0,out,in); // in_sbar = in - in_s
|
||||
|
||||
HermOp(in_sbar,out);
|
||||
ProjectToSubspace(out,PleftProj); // Mssbar in_sbar (project)
|
||||
|
||||
ApplyInverse (PleftProj,PleftMss_proj); // Mss^{-1} Mssbar
|
||||
PromoteFromSubspace(PleftMss_proj,out); //
|
||||
|
||||
axpy(out,-1.0,out,in_sbar); // in_sbar - Mss^{-1} Mssbar in_sbar
|
||||
}
|
||||
virtual void Pleft (Field & in,Field & out){
|
||||
// P_L = [ 1 -Mbs Mss^-1]
|
||||
// [ 0 0 ]
|
||||
Field in_sbar(grid);
|
||||
Field tmp2(grid);
|
||||
Field Mtmp(grid);
|
||||
|
||||
ProjectToSubspace(in,PleftProj);
|
||||
PromoteFromSubspace(PleftProj,out);
|
||||
axpy(in_sbar,-1.0,out,in); // in_sbar = in - in_s
|
||||
|
||||
ApplyInverse(PleftProj,PleftMss_proj); // Mss^{-1} in_s
|
||||
PromoteFromSubspace(PleftMss_proj,out);
|
||||
|
||||
HermOp(out,Mtmp);
|
||||
|
||||
ProjectToSubspace(Mtmp,PleftProj); // Msbar s Mss^{-1}
|
||||
PromoteFromSubspace(PleftProj,tmp2);
|
||||
|
||||
axpy(out,-1.0,tmp2,Mtmp);
|
||||
axpy(out,-1.0,out,in_sbar); // in_sbar - Msbars Mss^{-1} in_s
|
||||
}
|
||||
}
|
||||
|
||||
template<class Field>
|
||||
class TwoLevelFlexiblePcgADef2 : public TwoLevelFlexiblePcg<Field> {
|
||||
public:
|
||||
virtual void M(Field & in,Field & out,Field & tmp){
|
||||
|
||||
}
|
||||
virtual void M1(Field & in, Field & out,Field & tmp,Field & mp){
|
||||
|
||||
}
|
||||
virtual void M2(Field & in, Field & out){
|
||||
|
||||
}
|
||||
virtual RealD M3(Field & p, Field & mp,Field & mmp, Field & tmp){
|
||||
|
||||
}
|
||||
virtual void Vstart(Field & in, Field & src, Field & r, Field & mp, Field & mmp, Field & tmp){
|
||||
|
||||
}
|
||||
}
|
||||
/*
|
||||
template<class Field>
|
||||
class TwoLevelFlexiblePcgAD : public TwoLevelFlexiblePcg<Field> {
|
||||
public:
|
||||
virtual void M(Field & in,Field & out,Field & tmp);
|
||||
virtual void M1(Field & in, Field & out,Field & tmp,Field & mp);
|
||||
virtual void M2(Field & in, Field & out);
|
||||
virtual RealD M3(Field & p, Field & mp,Field & mmp, Field & tmp);
|
||||
virtual void Vstart(Field & in, Field & src, Field & r, Field & mp, Field & mmp, Field & tmp);
|
||||
}
|
||||
|
||||
template<class Field>
|
||||
class TwoLevelFlexiblePcgDef1 : public TwoLevelFlexiblePcg<Field> {
|
||||
public:
|
||||
virtual void M(Field & in,Field & out,Field & tmp);
|
||||
virtual void M1(Field & in, Field & out,Field & tmp,Field & mp);
|
||||
virtual void M2(Field & in, Field & out);
|
||||
virtual RealD M3(Field & p, Field & mp,Field & mmp, Field & tmp);
|
||||
virtual void Vstart(Field & in, Field & src, Field & r, Field & mp, Field & mmp, Field & tmp);
|
||||
virtual void Vout (Field & in, Field & out,Field & src,Field & tmp);
|
||||
}
|
||||
|
||||
template<class Field>
|
||||
class TwoLevelFlexiblePcgDef2 : public TwoLevelFlexiblePcg<Field> {
|
||||
public:
|
||||
virtual void M(Field & in,Field & out,Field & tmp);
|
||||
virtual void M1(Field & in, Field & out,Field & tmp,Field & mp);
|
||||
virtual void M2(Field & in, Field & out);
|
||||
virtual RealD M3(Field & p, Field & mp,Field & mmp, Field & tmp);
|
||||
virtual void Vstart(Field & in, Field & src, Field & r, Field & mp, Field & mmp, Field & tmp);
|
||||
}
|
||||
|
||||
template<class Field>
|
||||
class TwoLevelFlexiblePcgV11: public TwoLevelFlexiblePcg<Field> {
|
||||
public:
|
||||
virtual void M(Field & in,Field & out,Field & tmp);
|
||||
virtual void M1(Field & in, Field & out,Field & tmp,Field & mp);
|
||||
virtual void M2(Field & in, Field & out);
|
||||
virtual RealD M3(Field & p, Field & mp,Field & mmp, Field & tmp);
|
||||
virtual void Vstart(Field & in, Field & src, Field & r, Field & mp, Field & mmp, Field & tmp);
|
||||
}
|
||||
*/
|
||||
#endif
|
||||
@@ -0,0 +1,660 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./lib/algorithms/iterative/AdefGeneric.h
|
||||
|
||||
Copyright (C) 2015
|
||||
|
||||
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
#include <Grid/algorithms/multigrid/MrhsPreconditioner.h>
|
||||
|
||||
/*
|
||||
* Compared to Tang-2009: P=Pleft. P^T = PRight Q=MssInv.
|
||||
* Script A = SolverMatrix
|
||||
* Script P = Preconditioner
|
||||
*
|
||||
* Implement ADEF-2
|
||||
*
|
||||
* Vstart = P^Tx + Qb
|
||||
* M1 = P^TM + Q
|
||||
* M2=M3=1
|
||||
*/
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Two-level CG family on a vector of right-hand sides, with the
|
||||
// preconditioner (M1 and Vstart) as an MrhsPreconditioner object:
|
||||
// fPcg flexible PCG, one Krylov per rhs, shared M1 call
|
||||
// PrecBlockCGrQ preconditioned BlockCGrQ (arXiv:2409.03904 s2.2);
|
||||
// assumes a stationary preconditioner; not the
|
||||
// production choice (its linalg grows as nrhs^2)
|
||||
// Also a LinearFunction<Field>: one rhs is the nrhs=1 case.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
enum class MrhsCGAlgorithm { fPcg, PrecBlockCGrQ };
|
||||
|
||||
template<class Field>
|
||||
class TwoLevelCGmrhs : public LinearFunction<Field>
|
||||
{
|
||||
public:
|
||||
using LinearFunction<Field>::operator();
|
||||
RealD Tolerance;
|
||||
Integer MaxIterations;
|
||||
GridBase *grid;
|
||||
MrhsCGAlgorithm Algorithm;
|
||||
|
||||
LinearOperatorBase<Field> &_FineLinop;
|
||||
MrhsPreconditioner<Field> &_Precon;
|
||||
MultiRHSBlockCGLinalg<Field> _BlockCGLinalg;
|
||||
|
||||
TwoLevelCGmrhs(RealD tol,
|
||||
Integer maxit,
|
||||
LinearOperatorBase<Field> &FineLinop,
|
||||
MrhsPreconditioner<Field> &Precon,
|
||||
GridBase *fine,
|
||||
MrhsCGAlgorithm alg = MrhsCGAlgorithm::fPcg) :
|
||||
Tolerance(tol),
|
||||
MaxIterations(maxit),
|
||||
Algorithm(alg),
|
||||
_FineLinop(FineLinop),
|
||||
_Precon(Precon)
|
||||
{
|
||||
grid = fine;
|
||||
};
|
||||
|
||||
// mrhs entry point
|
||||
virtual void operator() (std::vector<Field> &src, std::vector<Field> &x)
|
||||
{
|
||||
if ( Algorithm == MrhsCGAlgorithm::PrecBlockCGrQ ) SolvePrecBlockCG(src,x);
|
||||
else SolveSingleSystem(src,x);
|
||||
}
|
||||
// LinearFunction entry points
|
||||
virtual void operator() (const Field &src, Field &x)
|
||||
{
|
||||
std::vector<Field> S(1,src);
|
||||
std::vector<Field> X(1,x);
|
||||
(*this)(S,X);
|
||||
x = X[0];
|
||||
}
|
||||
virtual void operator() (const std::vector<Field> &src, std::vector<Field> &x)
|
||||
{
|
||||
std::vector<Field> S(src);
|
||||
(*this)(S,x);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Thin QR factorisation (google it)
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//Dimensions
|
||||
// R_{ferm x Nblock} = Q_{ferm x Nblock} x C_{Nblock x Nblock} -> ferm x Nblock
|
||||
//
|
||||
// Rdag R = m_rr = Herm = L L^dag <-- Cholesky decomposition (LLT routine in Eigen)
|
||||
//
|
||||
// Q C = R => Q = R C^{-1}
|
||||
//
|
||||
// Want Ident = Q^dag Q = C^{-dag} R^dag R C^{-1} = C^{-dag} L L^dag C^{-1} = 1_{Nblock x Nblock}
|
||||
//
|
||||
// Set C = L^{dag}, and then Q^dag Q = ident
|
||||
//
|
||||
// Checks:
|
||||
// Cdag C = Rdag R ; passes.
|
||||
// QdagQ = 1 ; passes
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
void ThinQRfact (Eigen::MatrixXcd &m_zz,
|
||||
Eigen::MatrixXcd &C,
|
||||
Eigen::MatrixXcd &Cinv,
|
||||
std::vector<Field> & Q,
|
||||
std::vector<Field> & MQ,
|
||||
const std::vector<Field> & Z,
|
||||
const std::vector<Field> & MZ)
|
||||
{
|
||||
RealD t0=usecond();
|
||||
_BlockCGLinalg.InnerProductMatrix(m_zz,MZ,Z);
|
||||
RealD t1=usecond();
|
||||
|
||||
m_zz = 0.5*(m_zz+m_zz.adjoint());
|
||||
|
||||
Eigen::MatrixXcd L = m_zz.llt().matrixL();
|
||||
|
||||
C = L.adjoint();
|
||||
Cinv = C.inverse();
|
||||
|
||||
RealD t3=usecond();
|
||||
_BlockCGLinalg.MulMatrix( Q,Cinv,Z);
|
||||
_BlockCGLinalg.MulMatrix(MQ,Cinv,MZ);
|
||||
RealD t4=usecond();
|
||||
std::cout << " ThinQRfact IP :"<< t1-t0<<" us"<<std::endl;
|
||||
std::cout << " ThinQRfact Eigen :"<< t3-t1<<" us"<<std::endl;
|
||||
std::cout << " ThinQRfact MulMat:"<< t4-t3<<" us"<<std::endl;
|
||||
}
|
||||
|
||||
virtual void SolvePrecBlockCG (std::vector<Field> &src, std::vector<Field> &X)
|
||||
{
|
||||
std::cout << GridLogMessage<<"HDCG: mrhs fPrecBlockcg starting"<<std::endl;
|
||||
src[0].Grid()->Barrier();
|
||||
int nrhs = src.size();
|
||||
// std::vector<RealD> f(nrhs);
|
||||
// std::vector<RealD> rtzp(nrhs);
|
||||
// std::vector<RealD> rtz(nrhs);
|
||||
// std::vector<RealD> a(nrhs);
|
||||
// std::vector<RealD> d(nrhs);
|
||||
// std::vector<RealD> b(nrhs);
|
||||
// std::vector<RealD> rptzp(nrhs);
|
||||
|
||||
////////////////////////////////////////////
|
||||
//Initial residual computation & set up
|
||||
////////////////////////////////////////////
|
||||
std::vector<RealD> ssq(nrhs);
|
||||
for(int rhs=0;rhs<nrhs;rhs++){
|
||||
ssq[rhs]=norm2(src[rhs]); GRID_ASSERT(ssq[rhs]!=0.0);
|
||||
}
|
||||
|
||||
///////////////////////////
|
||||
// Fields -- eliminate duplicates between fPcg and block cg
|
||||
///////////////////////////
|
||||
std::vector<Field> Mtmp(nrhs,grid);
|
||||
std::vector<Field> tmp(nrhs,grid);
|
||||
std::vector<Field> Z(nrhs,grid); // Rename Z to R
|
||||
std::vector<Field> MZ(nrhs,grid); // Rename MZ to Z
|
||||
std::vector<Field> Q(nrhs,grid); //
|
||||
std::vector<Field> MQ(nrhs,grid); // Rename to P
|
||||
std::vector<Field> D(nrhs,grid);
|
||||
std::vector<Field> AD(nrhs,grid);
|
||||
|
||||
/************************************************************************
|
||||
* Preconditioned Block conjugate gradient rQ
|
||||
* Generalise Sebastien Birk Thesis, after Dubrulle 2001.
|
||||
* Introduce preconditioning following Saad Ch9
|
||||
************************************************************************
|
||||
* Dimensions:
|
||||
*
|
||||
* X,B etc... ==(Nferm x nrhs)
|
||||
* Matrix A==(Nferm x Nferm)
|
||||
*
|
||||
* Nferm = Nspin x Ncolour x Ncomplex x Nlattice_site
|
||||
* QC => Thin QR factorisation (google it)
|
||||
*
|
||||
* R = B-AX
|
||||
* Z = Mi R
|
||||
* QC = Z
|
||||
* D = Q
|
||||
* for k:
|
||||
* R = AD
|
||||
* Z = Mi R
|
||||
* M = [D^dag R]^{-1}
|
||||
* X = X + D M C
|
||||
* QS = Q - Z.M
|
||||
* D = Q + D S^dag
|
||||
* C = S C
|
||||
*/
|
||||
Eigen::MatrixXcd m_DZ = Eigen::MatrixXcd::Identity(nrhs,nrhs);
|
||||
Eigen::MatrixXcd m_M = Eigen::MatrixXcd::Identity(nrhs,nrhs);
|
||||
Eigen::MatrixXcd m_zz = Eigen::MatrixXcd::Zero(nrhs,nrhs);
|
||||
Eigen::MatrixXcd m_rr = Eigen::MatrixXcd::Zero(nrhs,nrhs);
|
||||
|
||||
Eigen::MatrixXcd m_C = Eigen::MatrixXcd::Zero(nrhs,nrhs);
|
||||
Eigen::MatrixXcd m_Cinv = Eigen::MatrixXcd::Zero(nrhs,nrhs);
|
||||
Eigen::MatrixXcd m_S = Eigen::MatrixXcd::Zero(nrhs,nrhs);
|
||||
Eigen::MatrixXcd m_Sinv = Eigen::MatrixXcd::Zero(nrhs,nrhs);
|
||||
|
||||
Eigen::MatrixXcd m_tmp = Eigen::MatrixXcd::Identity(nrhs,nrhs);
|
||||
Eigen::MatrixXcd m_tmp1 = Eigen::MatrixXcd::Identity(nrhs,nrhs);
|
||||
|
||||
GridStopWatch HDCGTimer;
|
||||
|
||||
//////////////////////////
|
||||
// x0 = Vstart -- possibly modify guess
|
||||
//////////////////////////
|
||||
_Precon.Vstart(X,src);
|
||||
|
||||
//////////////////////////
|
||||
// R = B-AX
|
||||
//////////////////////////
|
||||
for(int rhs=0;rhs<nrhs;rhs++){
|
||||
// r0 = b -A x0
|
||||
_FineLinop.HermOp(X[rhs],tmp[rhs]);
|
||||
axpy (Z[rhs], -1.0,tmp[rhs], src[rhs]); // Computes R=Z=src - A X0
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// Compute MZ = M1 Z = M1 B - M1 A x0
|
||||
//////////////////////////////////
|
||||
_Precon(Z,MZ);
|
||||
|
||||
//////////////////////////////////
|
||||
// QC = Z
|
||||
//////////////////////////////////
|
||||
ThinQRfact (m_zz, m_C, m_Cinv, Q, MQ, Z, MZ);
|
||||
|
||||
//////////////////////////////////
|
||||
// D=MQ
|
||||
//////////////////////////////////
|
||||
for(int b=0;b<nrhs;b++) D[b]=MQ[b]; // LLT rotation of the MZ basis of search dirs
|
||||
|
||||
std::cout << GridLogMessage<<"PrecBlockCGrQ vec computed initial residual and QR fact " <<std::endl;
|
||||
|
||||
_Precon.ResetTimers();
|
||||
|
||||
GridStopWatch M1Timer;
|
||||
GridStopWatch M2Timer;
|
||||
GridStopWatch M3Timer;
|
||||
GridStopWatch LinalgTimer;
|
||||
GridStopWatch InnerProdTimer;
|
||||
|
||||
HDCGTimer.Start();
|
||||
|
||||
std::vector<RealD> rn(nrhs);
|
||||
for (int k=0;k<=MaxIterations;k++){
|
||||
|
||||
////////////////////
|
||||
// Z = AD
|
||||
////////////////////
|
||||
M3Timer.Start();
|
||||
for(int b=0;b<nrhs;b++) _FineLinop.HermOp(D[b], Z[b]);
|
||||
M3Timer.Stop();
|
||||
|
||||
////////////////////
|
||||
// MZ = M1 Z <==== the Multigrid preconditioner
|
||||
////////////////////
|
||||
M1Timer.Start();
|
||||
_Precon(Z,MZ);
|
||||
M1Timer.Stop();
|
||||
|
||||
////////////////////
|
||||
// M = [D^dag Z]^{-1} = (<Ddag MZ>_M)^{-1} inner prod, generalising Saad derivation of Precon CG
|
||||
////////////////////
|
||||
InnerProdTimer.Start();
|
||||
_BlockCGLinalg.InnerProductMatrix(m_DZ,D,Z);
|
||||
InnerProdTimer.Stop();
|
||||
m_M = m_DZ.inverse();
|
||||
|
||||
///////////////////////////
|
||||
// X = X + D MC
|
||||
///////////////////////////
|
||||
m_tmp = m_M * m_C;
|
||||
LinalgTimer.Start();
|
||||
_BlockCGLinalg.MaddMatrix(X,m_tmp, D,X); // D are the search directions and X takes the updates
|
||||
LinalgTimer.Stop();
|
||||
|
||||
///////////////////////////
|
||||
// QS = Q - M Z
|
||||
// (MQ) S = MQ - M (M1Z)
|
||||
///////////////////////////
|
||||
LinalgTimer.Start();
|
||||
_BlockCGLinalg.MaddMatrix(tmp ,m_M, Z, Q,-1.0);
|
||||
_BlockCGLinalg.MaddMatrix(Mtmp,m_M,MZ,MQ,-1.0);
|
||||
ThinQRfact (m_zz, m_S, m_Sinv, Q, MQ, tmp, Mtmp);
|
||||
LinalgTimer.Stop();
|
||||
|
||||
////////////////////////////
|
||||
// D = MQ + D S^dag
|
||||
////////////////////////////
|
||||
m_tmp = m_S.adjoint();
|
||||
LinalgTimer.Start();
|
||||
_BlockCGLinalg.MaddMatrix(D,m_tmp,D,MQ);
|
||||
LinalgTimer.Stop();
|
||||
|
||||
////////////////////////////
|
||||
// C = S C
|
||||
////////////////////////////
|
||||
m_C = m_S*m_C;
|
||||
|
||||
////////////////////////////
|
||||
// convergence monitor
|
||||
////////////////////////////
|
||||
m_rr = m_C.adjoint() * m_C;
|
||||
|
||||
|
||||
RealD max_resid=0;
|
||||
RealD rrsum=0;
|
||||
RealD sssum=0;
|
||||
RealD rr;
|
||||
|
||||
for(int b=0;b<nrhs;b++) {
|
||||
rrsum+=real(m_rr(b,b));
|
||||
sssum+=ssq[b];
|
||||
rr = real(m_rr(b,b))/ssq[b];
|
||||
if ( rr > max_resid ) max_resid = rr;
|
||||
}
|
||||
std::cout << GridLogMessage <<
|
||||
"\t Prec BlockCGrQ Iteration "<<k<<" ave resid "<< std::sqrt(rrsum/sssum) << " max "<< std::sqrt(max_resid) <<std::endl;
|
||||
|
||||
|
||||
if ( max_resid < Tolerance*Tolerance ) {
|
||||
|
||||
HDCGTimer.Stop();
|
||||
std::cout<<GridLogMessage<<"HDCG: mrhs PrecBlockCGrQ converged in "<<k<<" iterations and "<<HDCGTimer.Elapsed()<<std::endl;;
|
||||
std::cout<<GridLogMessage<<"HDCG: mrhs PrecBlockCGrQ : Linalg "<<LinalgTimer.Elapsed()<<std::endl;;
|
||||
std::cout<<GridLogMessage<<"HDCG: mrhs PrecBlockCGrQ : fine H "<<M3Timer.Elapsed()<<std::endl;;
|
||||
std::cout<<GridLogMessage<<"HDCG: mrhs PrecBlockCGrQ : prec M1 "<<M1Timer.Elapsed()<<std::endl;;
|
||||
_Precon.ReportTimers("HDCG: mrhs PrecBlockCGrQ : ");
|
||||
|
||||
for(int rhs=0;rhs<nrhs;rhs++){
|
||||
|
||||
_FineLinop.HermOp(X[rhs],tmp[rhs]);
|
||||
|
||||
Field mytmp(grid);
|
||||
axpy(mytmp,-1.0,src[rhs],tmp[rhs]);
|
||||
|
||||
RealD xnorm = sqrt(norm2(X[rhs]));
|
||||
RealD srcnorm = sqrt(norm2(src[rhs]));
|
||||
RealD tmpnorm = sqrt(norm2(mytmp));
|
||||
RealD true_residual = tmpnorm/srcnorm;
|
||||
std::cout<<GridLogMessage
|
||||
<<"HDCG: true residual ["<<rhs<<"] is "<<true_residual
|
||||
<<" solution "<<xnorm
|
||||
<<" source "<<srcnorm
|
||||
<<std::endl;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
HDCGTimer.Stop();
|
||||
std::cout<<GridLogMessage<<"HDCG: PrecBlockCGrQ not converged "<<HDCGTimer.Elapsed()<<std::endl;
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
|
||||
virtual void SolveSingleSystem (std::vector<Field> &src, std::vector<Field> &x)
|
||||
{
|
||||
std::cout << GridLogMessage<<"HDCG: mrhs fPcg starting"<<std::endl;
|
||||
src[0].Grid()->Barrier();
|
||||
int nrhs = src.size();
|
||||
std::vector<RealD> f(nrhs);
|
||||
std::vector<RealD> rtzp(nrhs);
|
||||
std::vector<RealD> rtz(nrhs);
|
||||
std::vector<RealD> a(nrhs);
|
||||
std::vector<RealD> d(nrhs);
|
||||
std::vector<RealD> b(nrhs);
|
||||
std::vector<RealD> rptzp(nrhs);
|
||||
/////////////////////////////
|
||||
// Set up history vectors
|
||||
/////////////////////////////
|
||||
int mmax = 3;
|
||||
|
||||
std::vector<std::vector<Field> > p(nrhs); for(int r=0;r<nrhs;r++) p[r].resize(mmax,grid);
|
||||
std::vector<std::vector<Field> > mmp(nrhs); for(int r=0;r<nrhs;r++) mmp[r].resize(mmax,grid);
|
||||
std::vector<std::vector<RealD> > pAp(nrhs); for(int r=0;r<nrhs;r++) pAp[r].resize(mmax);
|
||||
|
||||
std::vector<Field> z(nrhs,grid);
|
||||
std::vector<Field> mp (nrhs,grid);
|
||||
std::vector<Field> r (nrhs,grid);
|
||||
std::vector<Field> mu (nrhs,grid);
|
||||
|
||||
//Initial residual computation & set up
|
||||
std::vector<RealD> src_nrm(nrhs);
|
||||
for(int rhs=0;rhs<nrhs;rhs++) {
|
||||
src_nrm[rhs]=norm2(src[rhs]);
|
||||
GRID_ASSERT(src_nrm[rhs]!=0.0);
|
||||
}
|
||||
std::vector<RealD> tn(nrhs);
|
||||
|
||||
GridStopWatch HDCGTimer;
|
||||
//////////////////////////
|
||||
// x0 = Vstart -- possibly modify guess
|
||||
//////////////////////////
|
||||
_Precon.Vstart(x,src);
|
||||
|
||||
for(int rhs=0;rhs<nrhs;rhs++){
|
||||
// r0 = b -A x0
|
||||
_FineLinop.HermOp(x[rhs],mmp[rhs][0]);
|
||||
axpy (r[rhs], -1.0,mmp[rhs][0], src[rhs]); // Recomputes r=src-Ax0
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// Compute z = M1 x
|
||||
//////////////////////////////////
|
||||
// This needs a multiRHS version for acceleration
|
||||
_Precon(r,z);
|
||||
|
||||
std::vector<RealD> ssq(nrhs);
|
||||
std::vector<RealD> rsq(nrhs);
|
||||
std::vector<Field> pp(nrhs,grid);
|
||||
|
||||
for(int rhs=0;rhs<nrhs;rhs++){
|
||||
rtzp[rhs] =real(innerProduct(r[rhs],z[rhs]));
|
||||
p[rhs][0]=z[rhs];
|
||||
ssq[rhs]=norm2(src[rhs]);
|
||||
rsq[rhs]= ssq[rhs]*Tolerance*Tolerance;
|
||||
// std::cout << GridLogMessage<<"mrhs HDCG: "<<rhs<<" k=0 residual "<<rtzp[rhs]<<" rsq "<<rsq[rhs]<<"\n";
|
||||
}
|
||||
|
||||
_Precon.ResetTimers();
|
||||
|
||||
GridStopWatch M1Timer;
|
||||
GridStopWatch M2Timer;
|
||||
GridStopWatch M3Timer;
|
||||
GridStopWatch LinalgTimer;
|
||||
|
||||
HDCGTimer.Start();
|
||||
|
||||
std::vector<RealD> rn(nrhs);
|
||||
for (int k=0;k<=MaxIterations;k++){
|
||||
|
||||
int peri_k = k % mmax;
|
||||
int peri_kp = (k+1) % mmax;
|
||||
|
||||
for(int rhs=0;rhs<nrhs;rhs++){
|
||||
rtz[rhs]=rtzp[rhs];
|
||||
M3Timer.Start();
|
||||
d[rhs]= PcgM3(p[rhs][peri_k],mmp[rhs][peri_k]);
|
||||
M3Timer.Stop();
|
||||
a[rhs] = rtz[rhs]/d[rhs];
|
||||
|
||||
LinalgTimer.Start();
|
||||
// Memorise this
|
||||
pAp[rhs][peri_k] = d[rhs];
|
||||
|
||||
axpy(x[rhs],a[rhs],p[rhs][peri_k],x[rhs]);
|
||||
rn[rhs] = axpy_norm(r[rhs],-a[rhs],mmp[rhs][peri_k],r[rhs]);
|
||||
LinalgTimer.Stop();
|
||||
}
|
||||
|
||||
// Compute z = M x (for *all* RHS)
|
||||
M1Timer.Start();
|
||||
_Precon(r,z);
|
||||
M1Timer.Stop();
|
||||
|
||||
RealD max_rn=0.0;
|
||||
LinalgTimer.Start();
|
||||
for(int rhs=0;rhs<nrhs;rhs++){
|
||||
|
||||
rtzp[rhs] =real(innerProduct(r[rhs],z[rhs]));
|
||||
|
||||
// std::cout << GridLogMessage<<"HDCG::fPcg rhs"<<rhs<<" iteration "<<k<<" : inner rtzp "<<rtzp[rhs]<<"\n";
|
||||
mu[rhs]=z[rhs];
|
||||
|
||||
p[rhs][peri_kp]=mu[rhs];
|
||||
|
||||
// Standard search direction p == z + b p
|
||||
b[rhs] = (rtzp[rhs])/rtz[rhs];
|
||||
|
||||
int northog = (k>mmax-1)?(mmax-1):k; // This is the fCG-Tr(mmax-1) algorithm
|
||||
for(int back=0; back < northog; back++){
|
||||
int peri_back = (k-back)%mmax;
|
||||
RealD pbApk= real(innerProduct(mmp[rhs][peri_back],p[rhs][peri_kp]));
|
||||
RealD beta = -pbApk/pAp[rhs][peri_back];
|
||||
axpy(p[rhs][peri_kp],beta,p[rhs][peri_back],p[rhs][peri_kp]);
|
||||
}
|
||||
|
||||
RealD rrn=sqrt(rn[rhs]/ssq[rhs]);
|
||||
RealD rtn=sqrt(rtz[rhs]/ssq[rhs]);
|
||||
RealD rtnp=sqrt(rtzp[rhs]/ssq[rhs]);
|
||||
|
||||
std::cout<<GridLogMessage<<"HDCG:fPcg rhs "<<rhs<<" k= "<<k<<" residual = "<<rrn<<"\n";
|
||||
if ( rrn > max_rn ) max_rn = rrn;
|
||||
}
|
||||
LinalgTimer.Stop();
|
||||
|
||||
// Stopping condition based on worst case
|
||||
if ( max_rn <= Tolerance ) {
|
||||
|
||||
HDCGTimer.Stop();
|
||||
std::cout<<GridLogMessage<<"HDCG: mrhs fPcg converged in "<<k<<" iterations and "<<HDCGTimer.Elapsed()<<std::endl;;
|
||||
std::cout<<GridLogMessage<<"HDCG: mrhs fPcg : Linalg "<<LinalgTimer.Elapsed()<<std::endl;;
|
||||
std::cout<<GridLogMessage<<"HDCG: mrhs fPcg : fine M3 "<<M3Timer.Elapsed()<<std::endl;;
|
||||
std::cout<<GridLogMessage<<"HDCG: mrhs fPcg : prec M1 "<<M1Timer.Elapsed()<<std::endl;;
|
||||
_Precon.ReportTimers("HDCG: mrhs fPcg : ");
|
||||
|
||||
for(int rhs=0;rhs<nrhs;rhs++){
|
||||
_FineLinop.HermOp(x[rhs],mmp[rhs][0]);
|
||||
Field tmp(grid);
|
||||
axpy(tmp,-1.0,src[rhs],mmp[rhs][0]);
|
||||
|
||||
RealD mmpnorm = sqrt(norm2(mmp[rhs][0]));
|
||||
RealD xnorm = sqrt(norm2(x[rhs]));
|
||||
RealD srcnorm = sqrt(norm2(src[rhs]));
|
||||
RealD tmpnorm = sqrt(norm2(tmp));
|
||||
RealD true_residual = tmpnorm/srcnorm;
|
||||
std::cout<<GridLogMessage
|
||||
<<"HDCG: true residual ["<<rhs<<"] is "<<true_residual
|
||||
<<" solution "<<xnorm
|
||||
<<" source "<<srcnorm
|
||||
<<" mmp "<<mmpnorm
|
||||
<<std::endl;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
HDCGTimer.Stop();
|
||||
std::cout<<GridLogMessage<<"HDCG: not converged "<<HDCGTimer.Elapsed()<<std::endl;
|
||||
for(int rhs=0;rhs<nrhs;rhs++){
|
||||
RealD xnorm = sqrt(norm2(x[rhs]));
|
||||
RealD srcnorm = sqrt(norm2(src[rhs]));
|
||||
std::cout<<GridLogMessage<<"HDCG: non-converged solution "<<xnorm<<" source "<<srcnorm<<std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
RealD PcgM3(const Field & p, Field & mmp){
|
||||
RealD dd;
|
||||
_FineLinop.HermOp(p,mmp);
|
||||
ComplexD dot = innerProduct(p,mmp);
|
||||
dd=real(dot);
|
||||
return dd;
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// ADEF-2 as an mrhs preconditioner object (Tang, Nabben, Vuik, Erlangga):
|
||||
// M1 = [1 - Q A] M + Q with Q = P A_c^{-1} P^dag (deflated coarse solve)
|
||||
// Vstart = Q b the coarse-corrected start (precise coarse solve)
|
||||
// The coarse space is the D+1 multiRHS field throughout: the projector's
|
||||
// mixed overloads take the vector of fine fields straight to it, and the
|
||||
// deflator and coarse solver act on it. No per-rhs slices anywhere.
|
||||
// The D+1 grid follows the solver's Nrhs through SetCoarseGridMrhs.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Projector_t defaults to a transfer operator whose STORE matches Field.
|
||||
template<class Field, class CoarseField, class Projector_t = MultiRHSBlockProject<Field> >
|
||||
class MrhsADEF2Preconditioner : public MrhsPreconditioner<Field>
|
||||
{
|
||||
public:
|
||||
GridBase *grid; // fine
|
||||
GridBase *coarsegridmrhs; // D+1, rhs innermost
|
||||
LinearOperatorBase<Field> &_FineLinop;
|
||||
LinearFunction<Field> &_Smoother;
|
||||
LinearFunction<CoarseField> &_CoarseSolver; // in M1
|
||||
LinearFunction<CoarseField> &_CoarseSolverPrecise; // in Vstart
|
||||
Projector_t &_Projector; // store precision need not match Field
|
||||
MultiRHSDeflation<CoarseField> &_Deflator; // nev==0: no deflation, zero coarse guess
|
||||
|
||||
MrhsADEF2Preconditioner(LinearOperatorBase<Field> &FineLinop,
|
||||
LinearFunction<Field> &Smoother,
|
||||
LinearFunction<CoarseField> &CoarseSolver,
|
||||
LinearFunction<CoarseField> &CoarseSolverPrecise,
|
||||
Projector_t &Projector,
|
||||
MultiRHSDeflation<CoarseField> &Deflator,
|
||||
GridBase *CoarseGridMrhs,
|
||||
GridBase *FineGrid) :
|
||||
_FineLinop(FineLinop), _Smoother(Smoother),
|
||||
_CoarseSolver(CoarseSolver), _CoarseSolverPrecise(CoarseSolverPrecise),
|
||||
_Projector(Projector), _Deflator(Deflator)
|
||||
{
|
||||
// The fine grid is given, NOT taken from the projector: the projector's
|
||||
// grid carries its STORE's layout, which need not be this Field's (an
|
||||
// fp32 preconditioner may project through an fp64 store, or the reverse).
|
||||
grid = FineGrid;
|
||||
coarsegridmrhs = CoarseGridMrhs;
|
||||
};
|
||||
// Store matches Field: the projector's grid IS this Field's grid.
|
||||
MrhsADEF2Preconditioner(LinearOperatorBase<Field> &FineLinop,
|
||||
LinearFunction<Field> &Smoother,
|
||||
LinearFunction<CoarseField> &CoarseSolver,
|
||||
LinearFunction<CoarseField> &CoarseSolverPrecise,
|
||||
Projector_t &Projector,
|
||||
MultiRHSDeflation<CoarseField> &Deflator,
|
||||
GridBase *CoarseGridMrhs) :
|
||||
MrhsADEF2Preconditioner(FineLinop,Smoother,CoarseSolver,CoarseSolverPrecise,
|
||||
Projector,Deflator,CoarseGridMrhs,Projector.fine_grid) {};
|
||||
|
||||
void SetCoarseGridMrhs(GridBase *CoarseGridMrhs) { coarsegridmrhs = CoarseGridMrhs; }
|
||||
|
||||
// The coarse solver's starting guess: deflated, or zero
|
||||
void CoarseGuess(CoarseField &src,CoarseField &guess)
|
||||
{
|
||||
this->DeflateTimer.Start();
|
||||
if ( _Deflator.nev > 0 ) _Deflator.DeflateSources(src,guess);
|
||||
else guess = Zero();
|
||||
this->DeflateTimer.Stop();
|
||||
}
|
||||
// x_0 = Q b
|
||||
virtual void Vstart(std::vector<Field> &x,std::vector<Field> &src)
|
||||
{
|
||||
CoarseField Csrc(coarsegridmrhs), Csol(coarsegridmrhs);
|
||||
this->ProjectTimer.Start(); _Projector.blockProject(src,Csrc); this->ProjectTimer.Stop();
|
||||
CoarseGuess(Csrc,Csol);
|
||||
this->CoarseTimer.Start(); _CoarseSolverPrecise(Csrc,Csol); this->CoarseTimer.Stop();
|
||||
this->PromoteTimer.Start(); _Projector.blockPromote(x,Csol); this->PromoteTimer.Stop();
|
||||
}
|
||||
// [1 - Q A] M in + Q in = Min + Q [in - A Min]
|
||||
virtual void operator()(std::vector<Field> &in,std::vector<Field> &out)
|
||||
{
|
||||
int nrhs=in.size();
|
||||
std::vector<Field> tmp(nrhs,grid), Min(nrhs,grid);
|
||||
CoarseField Csrc(coarsegridmrhs), Csol(coarsegridmrhs);
|
||||
|
||||
this->SmoothTimer.Start();
|
||||
for(int r=0;r<nrhs;r++) _Smoother(in[r],Min[r]);
|
||||
this->SmoothTimer.Stop();
|
||||
|
||||
this->FineTimer.Start();
|
||||
for(int r=0;r<nrhs;r++){
|
||||
_FineLinop.HermOp(Min[r],out[r]);
|
||||
axpy(tmp[r],-1.0,out[r],in[r]); // in - A Min
|
||||
}
|
||||
this->FineTimer.Stop();
|
||||
|
||||
this->ProjectTimer.Start(); _Projector.blockProject(tmp,Csrc); this->ProjectTimer.Stop();
|
||||
CoarseGuess(Csrc,Csol);
|
||||
this->CoarseTimer.Start(); _CoarseSolver(Csrc,Csol); this->CoarseTimer.Stop();
|
||||
this->PromoteTimer.Start(); _Projector.blockPromote(tmp,Csol); this->PromoteTimer.Stop();
|
||||
|
||||
this->FineTimer.Start();
|
||||
for(int r=0;r<nrhs;r++) axpy(out[r],1.0,Min[r],tmp[r]); // Min + Q[in - A Min]
|
||||
this->FineTimer.Stop();
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -47,7 +47,7 @@ class BiCGSTAB : public OperatorFunction<Field>
|
||||
public:
|
||||
using OperatorFunction<Field>::operator();
|
||||
|
||||
bool ErrorOnNoConverge; // throw an assert when the CG fails to converge.
|
||||
bool ErrorOnNoConverge; // throw an GRID_ASSERT when the CG fails to converge.
|
||||
// Defaults true.
|
||||
RealD Tolerance;
|
||||
Integer MaxIterations;
|
||||
@@ -77,7 +77,7 @@ class BiCGSTAB : public OperatorFunction<Field>
|
||||
|
||||
// Initial residual computation & set up
|
||||
RealD guess = norm2(psi);
|
||||
assert(std::isnan(guess) == 0);
|
||||
GRID_ASSERT(std::isnan(guess) == 0);
|
||||
|
||||
Linop.Op(psi, v);
|
||||
b = norm2(v);
|
||||
@@ -214,7 +214,7 @@ class BiCGSTAB : public OperatorFunction<Field>
|
||||
std::cout << GridLogMessage << "\tAxpyNorm " << AxpyNormTimer.Elapsed() << std::endl;
|
||||
std::cout << GridLogMessage << "\tLinearComb " << LinearCombTimer.Elapsed() << std::endl;
|
||||
|
||||
if(ErrorOnNoConverge){ assert(true_residual / Tolerance < 10000.0); }
|
||||
if(ErrorOnNoConverge){ GRID_ASSERT(true_residual / Tolerance < 10000.0); }
|
||||
|
||||
IterationsToComplete = k;
|
||||
|
||||
@@ -224,7 +224,7 @@ class BiCGSTAB : public OperatorFunction<Field>
|
||||
|
||||
std::cout << GridLogMessage << "BiCGSTAB did NOT converge" << std::endl;
|
||||
|
||||
if(ErrorOnNoConverge){ assert(0); }
|
||||
if(ErrorOnNoConverge){ GRID_ASSERT(0); }
|
||||
IterationsToComplete = k;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -31,6 +31,58 @@ directory
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
template<class Field>
|
||||
void InnerProductMatrix(Eigen::MatrixXcd &m , const std::vector<Field> &X, const std::vector<Field> &Y){
|
||||
typedef typename Field::scalar_type scomplex;
|
||||
int Nblock = X.size();
|
||||
for(int b=0;b<Nblock;b++){
|
||||
for(int bp=0;bp<Nblock;bp++) {
|
||||
m(b,bp) = innerProduct(X[b],Y[bp]);
|
||||
}}
|
||||
}
|
||||
template<class Field>
|
||||
void MaddMatrix(std::vector<Field> &AP, Eigen::MatrixXcd &m , const std::vector<Field> &X,const std::vector<Field> &Y,RealD scale=1.0){
|
||||
// Should make this cache friendly with site outermost, parallel_for
|
||||
// Deal with case AP aliases with either Y or X
|
||||
//
|
||||
//Could pack "X" and "AP" into a Nblock x Volume dense array.
|
||||
// AP(Nrhs x vol) = Y(Nrhs x vol) + scale * m(nrhs x nrhs) * X(nrhs*vol)
|
||||
typedef typename Field::scalar_type scomplex;
|
||||
int Nblock = AP.size();
|
||||
std::vector<Field> tmp(Nblock,X[0]);
|
||||
for(int b=0;b<Nblock;b++){
|
||||
tmp[b] = Y[b];
|
||||
for(int bp=0;bp<Nblock;bp++) {
|
||||
tmp[b] = tmp[b] +scomplex(scale*m(bp,b))*X[bp];
|
||||
}
|
||||
}
|
||||
for(int b=0;b<Nblock;b++){
|
||||
AP[b] = tmp[b];
|
||||
}
|
||||
}
|
||||
template<class Field>
|
||||
void MulMatrix(std::vector<Field> &AP, Eigen::MatrixXcd &m , const std::vector<Field> &X){
|
||||
// Should make this cache friendly with site outermost, parallel_for
|
||||
typedef typename Field::scalar_type scomplex;
|
||||
int Nblock = AP.size();
|
||||
for(int b=0;b<Nblock;b++){
|
||||
AP[b] = Zero();
|
||||
for(int bp=0;bp<Nblock;bp++) {
|
||||
AP[b] += scomplex(m(bp,b))*X[bp];
|
||||
}
|
||||
}
|
||||
}
|
||||
template<class Field>
|
||||
double normv(const std::vector<Field> &P){
|
||||
int Nblock = P.size();
|
||||
double nn = 0.0;
|
||||
for(int b=0;b<Nblock;b++) {
|
||||
nn+=norm2(P[b]);
|
||||
}
|
||||
return nn;
|
||||
}
|
||||
|
||||
|
||||
enum BlockCGtype { BlockCG, BlockCGrQ, CGmultiRHS, BlockCGVec, BlockCGrQVec };
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@@ -46,7 +98,7 @@ class BlockConjugateGradient : public OperatorFunction<Field> {
|
||||
int Nblock;
|
||||
|
||||
BlockCGtype CGtype;
|
||||
bool ErrorOnNoConverge; // throw an assert when the CG fails to converge.
|
||||
bool ErrorOnNoConverge; // throw an GRID_ASSERT when the CG fails to converge.
|
||||
// Defaults true.
|
||||
RealD Tolerance;
|
||||
Integer MaxIterations;
|
||||
@@ -87,10 +139,19 @@ void ThinQRfact (Eigen::MatrixXcd &m_rr,
|
||||
sliceInnerProductMatrix(m_rr,R,R,Orthog);
|
||||
|
||||
// Force manifest hermitian to avoid rounding related
|
||||
/*
|
||||
int rank=m_rr.rows();
|
||||
for(int r=0;r<rank;r++){
|
||||
for(int s=0;s<rank;s++){
|
||||
std::cout << "QR m_rr["<<r<<","<<s<<"] "<<m_rr(r,s)<<std::endl;
|
||||
}}
|
||||
*/
|
||||
m_rr = 0.5*(m_rr+m_rr.adjoint());
|
||||
|
||||
Eigen::MatrixXcd L = m_rr.llt().matrixL();
|
||||
|
||||
// ComplexD det = L.determinant();
|
||||
// std::cout << " Det m_rr "<<det<<std::endl;
|
||||
C = L.adjoint();
|
||||
Cinv = C.inverse();
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -110,11 +171,20 @@ void ThinQRfact (Eigen::MatrixXcd &m_rr,
|
||||
const std::vector<Field> & R)
|
||||
{
|
||||
InnerProductMatrix(m_rr,R,R);
|
||||
|
||||
/*
|
||||
int rank=m_rr.rows();
|
||||
for(int r=0;r<rank;r++){
|
||||
for(int s=0;s<rank;s++){
|
||||
std::cout << "QRvec m_rr["<<r<<","<<s<<"] "<<m_rr(r,s)<<std::endl;
|
||||
}}
|
||||
*/
|
||||
m_rr = 0.5*(m_rr+m_rr.adjoint());
|
||||
|
||||
Eigen::MatrixXcd L = m_rr.llt().matrixL();
|
||||
|
||||
// ComplexD det = L.determinant();
|
||||
// std::cout << " Det m_rr "<<det<<std::endl;
|
||||
|
||||
C = L.adjoint();
|
||||
Cinv = C.inverse();
|
||||
|
||||
@@ -131,7 +201,7 @@ void operator()(LinearOperatorBase<Field> &Linop, const Field &Src, Field &Psi)
|
||||
} else if (CGtype == CGmultiRHS ) {
|
||||
CGmultiRHSsolve(Linop,Src,Psi);
|
||||
} else {
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
}
|
||||
virtual void operator()(LinearOperatorBase<Field> &Linop, const std::vector<Field> &Src, std::vector<Field> &Psi)
|
||||
@@ -139,7 +209,7 @@ virtual void operator()(LinearOperatorBase<Field> &Linop, const std::vector<Fiel
|
||||
if ( CGtype == BlockCGrQVec ) {
|
||||
BlockCGrQsolveVec(Linop,Src,Psi);
|
||||
} else {
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,12 +256,13 @@ void BlockCGrQsolve(LinearOperatorBase<Field> &Linop, const Field &B, Field &X)
|
||||
sliceNorm(ssq,B,Orthog);
|
||||
RealD sssum=0;
|
||||
for(int b=0;b<Nblock;b++) sssum+=ssq[b];
|
||||
for(int b=0;b<Nblock;b++) std::cout << "src["<<b<<"]" << ssq[b] <<std::endl;
|
||||
|
||||
sliceNorm(residuals,B,Orthog);
|
||||
for(int b=0;b<Nblock;b++){ assert(std::isnan(residuals[b])==0); }
|
||||
for(int b=0;b<Nblock;b++){ GRID_ASSERT(std::isnan(residuals[b])==0); }
|
||||
|
||||
sliceNorm(residuals,X,Orthog);
|
||||
for(int b=0;b<Nblock;b++){ assert(std::isnan(residuals[b])==0); }
|
||||
for(int b=0;b<Nblock;b++){ GRID_ASSERT(std::isnan(residuals[b])==0); }
|
||||
|
||||
/************************************************************************
|
||||
* Block conjugate gradient rQ (Sebastien Birk Thesis, after Dubrulle 2001)
|
||||
@@ -221,6 +292,9 @@ void BlockCGrQsolve(LinearOperatorBase<Field> &Linop, const Field &B, Field &X)
|
||||
Linop.HermOp(X, AD);
|
||||
tmp = B - AD;
|
||||
|
||||
sliceNorm(residuals,tmp,Orthog);
|
||||
for(int b=0;b<Nblock;b++) std::cout << "res["<<b<<"]" << residuals[b] <<std::endl;
|
||||
|
||||
ThinQRfact (m_rr, m_C, m_Cinv, Q, tmp);
|
||||
D=Q;
|
||||
|
||||
@@ -236,6 +310,8 @@ void BlockCGrQsolve(LinearOperatorBase<Field> &Linop, const Field &B, Field &X)
|
||||
GridStopWatch SolverTimer;
|
||||
SolverTimer.Start();
|
||||
|
||||
RealD max_resid=0;
|
||||
|
||||
int k;
|
||||
for (k = 1; k <= MaxIterations; k++) {
|
||||
|
||||
@@ -280,7 +356,7 @@ void BlockCGrQsolve(LinearOperatorBase<Field> &Linop, const Field &B, Field &X)
|
||||
*/
|
||||
m_rr = m_C.adjoint() * m_C;
|
||||
|
||||
RealD max_resid=0;
|
||||
max_resid=0;
|
||||
RealD rrsum=0;
|
||||
RealD rr;
|
||||
|
||||
@@ -322,9 +398,11 @@ void BlockCGrQsolve(LinearOperatorBase<Field> &Linop, const Field &B, Field &X)
|
||||
}
|
||||
|
||||
}
|
||||
std::cout << GridLogMessage << "BlockConjugateGradient(rQ) did NOT converge" << std::endl;
|
||||
|
||||
if (ErrorOnNoConverge) assert(0);
|
||||
std::cout << GridLogMessage << "BlockConjugateGradient(rQ) did NOT converge "<<k<<" / "<<MaxIterations
|
||||
<<" residual "<< std::sqrt(max_resid)<< std::endl;
|
||||
|
||||
if (ErrorOnNoConverge) GRID_ASSERT(0);
|
||||
IterationsToComplete = k;
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@@ -360,10 +438,10 @@ void CGmultiRHSsolve(LinearOperatorBase<Field> &Linop, const Field &Src, Field &
|
||||
for(int b=0;b<Nblock;b++) sssum+=ssq[b];
|
||||
|
||||
sliceNorm(residuals,Src,Orthog);
|
||||
for(int b=0;b<Nblock;b++){ assert(std::isnan(residuals[b])==0); }
|
||||
for(int b=0;b<Nblock;b++){ GRID_ASSERT(std::isnan(residuals[b])==0); }
|
||||
|
||||
sliceNorm(residuals,Psi,Orthog);
|
||||
for(int b=0;b<Nblock;b++){ assert(std::isnan(residuals[b])==0); }
|
||||
for(int b=0;b<Nblock;b++){ GRID_ASSERT(std::isnan(residuals[b])==0); }
|
||||
|
||||
// Initial search dir is guess
|
||||
Linop.HermOp(Psi, AP);
|
||||
@@ -462,47 +540,10 @@ void CGmultiRHSsolve(LinearOperatorBase<Field> &Linop, const Field &Src, Field &
|
||||
}
|
||||
std::cout << GridLogMessage << "MultiRHSConjugateGradient did NOT converge" << std::endl;
|
||||
|
||||
if (ErrorOnNoConverge) assert(0);
|
||||
if (ErrorOnNoConverge) GRID_ASSERT(0);
|
||||
IterationsToComplete = k;
|
||||
}
|
||||
|
||||
void InnerProductMatrix(Eigen::MatrixXcd &m , const std::vector<Field> &X, const std::vector<Field> &Y){
|
||||
for(int b=0;b<Nblock;b++){
|
||||
for(int bp=0;bp<Nblock;bp++) {
|
||||
m(b,bp) = innerProduct(X[b],Y[bp]);
|
||||
}}
|
||||
}
|
||||
void MaddMatrix(std::vector<Field> &AP, Eigen::MatrixXcd &m , const std::vector<Field> &X,const std::vector<Field> &Y,RealD scale=1.0){
|
||||
// Should make this cache friendly with site outermost, parallel_for
|
||||
// Deal with case AP aliases with either Y or X
|
||||
std::vector<Field> tmp(Nblock,X[0]);
|
||||
for(int b=0;b<Nblock;b++){
|
||||
tmp[b] = Y[b];
|
||||
for(int bp=0;bp<Nblock;bp++) {
|
||||
tmp[b] = tmp[b] + scomplex(scale*m(bp,b))*X[bp];
|
||||
}
|
||||
}
|
||||
for(int b=0;b<Nblock;b++){
|
||||
AP[b] = tmp[b];
|
||||
}
|
||||
}
|
||||
void MulMatrix(std::vector<Field> &AP, Eigen::MatrixXcd &m , const std::vector<Field> &X){
|
||||
// Should make this cache friendly with site outermost, parallel_for
|
||||
for(int b=0;b<Nblock;b++){
|
||||
AP[b] = Zero();
|
||||
for(int bp=0;bp<Nblock;bp++) {
|
||||
AP[b] += scomplex(m(bp,b))*X[bp];
|
||||
}
|
||||
}
|
||||
}
|
||||
double normv(const std::vector<Field> &P){
|
||||
double nn = 0.0;
|
||||
for(int b=0;b<Nblock;b++) {
|
||||
nn+=norm2(P[b]);
|
||||
}
|
||||
return nn;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// BlockCGrQvec implementation:
|
||||
//--------------------------
|
||||
@@ -513,7 +554,7 @@ double normv(const std::vector<Field> &P){
|
||||
void BlockCGrQsolveVec(LinearOperatorBase<Field> &Linop, const std::vector<Field> &B, std::vector<Field> &X)
|
||||
{
|
||||
Nblock = B.size();
|
||||
assert(Nblock == X.size());
|
||||
GRID_ASSERT(Nblock == X.size());
|
||||
|
||||
std::cout<<GridLogMessage<<" Block Conjugate Gradient Vec rQ : Nblock "<<Nblock<<std::endl;
|
||||
|
||||
@@ -549,13 +590,14 @@ void BlockCGrQsolveVec(LinearOperatorBase<Field> &Linop, const std::vector<Field
|
||||
|
||||
RealD sssum=0;
|
||||
for(int b=0;b<Nblock;b++){ ssq[b] = norm2(B[b]);}
|
||||
for(int b=0;b<Nblock;b++){ std::cout << "ssq["<<b<<"] "<<ssq[b]<<std::endl;}
|
||||
for(int b=0;b<Nblock;b++) sssum+=ssq[b];
|
||||
|
||||
for(int b=0;b<Nblock;b++){ residuals[b] = norm2(B[b]);}
|
||||
for(int b=0;b<Nblock;b++){ assert(std::isnan(residuals[b])==0); }
|
||||
for(int b=0;b<Nblock;b++){ GRID_ASSERT(std::isnan(residuals[b])==0); }
|
||||
|
||||
for(int b=0;b<Nblock;b++){ residuals[b] = norm2(X[b]);}
|
||||
for(int b=0;b<Nblock;b++){ assert(std::isnan(residuals[b])==0); }
|
||||
for(int b=0;b<Nblock;b++){ GRID_ASSERT(std::isnan(residuals[b])==0); }
|
||||
|
||||
/************************************************************************
|
||||
* Block conjugate gradient rQ (Sebastien Birk Thesis, after Dubrulle 2001)
|
||||
@@ -585,6 +627,7 @@ void BlockCGrQsolveVec(LinearOperatorBase<Field> &Linop, const std::vector<Field
|
||||
for(int b=0;b<Nblock;b++) {
|
||||
Linop.HermOp(X[b], AD[b]);
|
||||
tmp[b] = B[b] - AD[b];
|
||||
std::cout << "r0["<<b<<"] "<<norm2(tmp[b])<<std::endl;
|
||||
}
|
||||
|
||||
ThinQRfact (m_rr, m_C, m_Cinv, Q, tmp);
|
||||
@@ -688,7 +731,7 @@ void BlockCGrQsolveVec(LinearOperatorBase<Field> &Linop, const std::vector<Field
|
||||
}
|
||||
std::cout << GridLogMessage << "BlockConjugateGradient(rQ) did NOT converge" << std::endl;
|
||||
|
||||
if (ErrorOnNoConverge) assert(0);
|
||||
if (ErrorOnNoConverge) GRID_ASSERT(0);
|
||||
IterationsToComplete = k;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class CommunicationAvoidingGeneralisedMinimalResidual : public OperatorFunction<
|
||||
public:
|
||||
using OperatorFunction<Field>::operator();
|
||||
|
||||
bool ErrorOnNoConverge; // Throw an assert when CAGMRES fails to converge,
|
||||
bool ErrorOnNoConverge; // Throw an GRID_ASSERT when CAGMRES fails to converge,
|
||||
// defaults to true
|
||||
|
||||
RealD Tolerance;
|
||||
@@ -82,7 +82,7 @@ class CommunicationAvoidingGeneralisedMinimalResidual : public OperatorFunction<
|
||||
conformable(psi, src);
|
||||
|
||||
RealD guess = norm2(psi);
|
||||
assert(std::isnan(guess) == 0);
|
||||
GRID_ASSERT(std::isnan(guess) == 0);
|
||||
|
||||
RealD cp;
|
||||
RealD ssq = norm2(src);
|
||||
@@ -137,7 +137,7 @@ class CommunicationAvoidingGeneralisedMinimalResidual : public OperatorFunction<
|
||||
std::cout << GridLogMessage << "CommunicationAvoidingGeneralisedMinimalResidual did NOT converge" << std::endl;
|
||||
|
||||
if (ErrorOnNoConverge)
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
|
||||
RealD outerLoopBody(LinearOperatorBase<Field> &LinOp, const Field &src, Field &psi, RealD rsq) {
|
||||
@@ -185,7 +185,7 @@ class CommunicationAvoidingGeneralisedMinimalResidual : public OperatorFunction<
|
||||
}
|
||||
}
|
||||
|
||||
assert(0); // Never reached
|
||||
GRID_ASSERT(0); // Never reached
|
||||
return cp;
|
||||
}
|
||||
|
||||
|
||||
@@ -38,13 +38,14 @@ NAMESPACE_BEGIN(Grid);
|
||||
// single input vec, single output vec.
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
template <class Field>
|
||||
class ConjugateGradient : public OperatorFunction<Field> {
|
||||
public:
|
||||
|
||||
using OperatorFunction<Field>::operator();
|
||||
|
||||
bool ErrorOnNoConverge; // throw an assert when the CG fails to converge.
|
||||
|
||||
bool ErrorOnNoConverge; // throw an GRID_ASSERT when the CG fails to converge.
|
||||
// Defaults true.
|
||||
RealD Tolerance;
|
||||
Integer MaxIterations;
|
||||
@@ -54,11 +55,26 @@ public:
|
||||
ConjugateGradient(RealD tol, Integer maxit, bool err_on_no_conv = true)
|
||||
: Tolerance(tol),
|
||||
MaxIterations(maxit),
|
||||
ErrorOnNoConverge(err_on_no_conv){};
|
||||
ErrorOnNoConverge(err_on_no_conv)
|
||||
{};
|
||||
|
||||
void operator()(LinearOperatorBase<Field> &Linop, const Field &src, Field &psi) {
|
||||
virtual void LogIteration(int k,RealD a,RealD b){
|
||||
// std::cout << "ConjugageGradient::LogIteration() "<<std::endl;
|
||||
};
|
||||
virtual void LogBegin(void){
|
||||
std::cout << "ConjugageGradient::LogBegin() "<<std::endl;
|
||||
};
|
||||
|
||||
GRID_TRACE("ConjugateGradient");
|
||||
void operator()(LinearOperatorBase<Field> &Linop, const Field &src, Field &psi) {
|
||||
|
||||
this->LogBegin();
|
||||
|
||||
GRID_TRACE("ConjugateGradient");
|
||||
GridStopWatch PreambleTimer;
|
||||
GridStopWatch ConstructTimer;
|
||||
GridStopWatch NormTimer;
|
||||
GridStopWatch AssignTimer;
|
||||
PreambleTimer.Start();
|
||||
psi.Checkerboard() = src.Checkerboard();
|
||||
|
||||
conformable(psi, src);
|
||||
@@ -66,22 +82,32 @@ public:
|
||||
RealD cp, c, a, d, b, ssq, qq;
|
||||
//RealD b_pred;
|
||||
|
||||
Field p(src);
|
||||
Field mmp(src);
|
||||
Field r(src);
|
||||
// Was doing copies
|
||||
ConstructTimer.Start();
|
||||
Field p (src.Grid());
|
||||
Field mmp(src.Grid());
|
||||
Field r (src.Grid());
|
||||
ConstructTimer.Stop();
|
||||
|
||||
// Initial residual computation & set up
|
||||
RealD guess = norm2(psi);
|
||||
assert(std::isnan(guess) == 0);
|
||||
|
||||
Linop.HermOpAndNorm(psi, mmp, d, b);
|
||||
|
||||
r = src - mmp;
|
||||
p = r;
|
||||
|
||||
a = norm2(p);
|
||||
cp = a;
|
||||
NormTimer.Start();
|
||||
ssq = norm2(src);
|
||||
RealD guess = norm2(psi);
|
||||
NormTimer.Stop();
|
||||
GRID_ASSERT(std::isnan(guess) == 0);
|
||||
AssignTimer.Start();
|
||||
if ( guess == 0.0 ) {
|
||||
r = src;
|
||||
p = r;
|
||||
a = ssq;
|
||||
} else {
|
||||
Linop.HermOpAndNorm(psi, mmp, d, b);
|
||||
r = src - mmp;
|
||||
p = r;
|
||||
a = norm2(p);
|
||||
}
|
||||
cp = a;
|
||||
AssignTimer.Stop();
|
||||
|
||||
// Handle trivial case of zero src
|
||||
if (ssq == 0.){
|
||||
@@ -111,6 +137,7 @@ public:
|
||||
std::cout << GridLogIterative << std::setprecision(8)
|
||||
<< "ConjugateGradient: k=0 residual " << cp << " target " << rsq << std::endl;
|
||||
|
||||
PreambleTimer.Stop();
|
||||
GridStopWatch LinalgTimer;
|
||||
GridStopWatch InnerTimer;
|
||||
GridStopWatch AxpyNormTimer;
|
||||
@@ -156,6 +183,7 @@ public:
|
||||
}
|
||||
LinearCombTimer.Stop();
|
||||
LinalgTimer.Stop();
|
||||
LogIteration(k,a,b);
|
||||
|
||||
IterationTimer.Stop();
|
||||
if ( (k % 500) == 0 ) {
|
||||
@@ -183,17 +211,22 @@ public:
|
||||
<< "\tTrue residual " << true_residual
|
||||
<< "\tTarget " << Tolerance << std::endl;
|
||||
|
||||
std::cout << GridLogMessage << "Time breakdown "<<std::endl;
|
||||
std::cout << GridLogMessage << "\tElapsed " << SolverTimer.Elapsed() <<std::endl;
|
||||
// std::cout << GridLogMessage << "\tPreamble " << PreambleTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\tPreamble " << PreambleTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\tConstruct " << ConstructTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\tNorm " << NormTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\tAssign " << AssignTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\tSolver " << SolverTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "Solver breakdown "<<std::endl;
|
||||
std::cout << GridLogMessage << "\tMatrix " << MatrixTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\tLinalg " << LinalgTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\tInner " << InnerTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\tAxpyNorm " << AxpyNormTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\tLinearComb " << LinearCombTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\t\tInner " << InnerTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\t\tAxpyNorm " << AxpyNormTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\t\tLinearComb " << LinearCombTimer.Elapsed() <<std::endl;
|
||||
|
||||
std::cout << GridLogDebug << "\tMobius flop rate " << DwfFlops/ usecs<< " Gflops " <<std::endl;
|
||||
|
||||
if (ErrorOnNoConverge) assert(true_residual / Tolerance < 10000.0);
|
||||
if (ErrorOnNoConverge) GRID_ASSERT(true_residual / Tolerance < 10000.0);
|
||||
|
||||
IterationsToComplete = k;
|
||||
TrueResidual = true_residual;
|
||||
@@ -202,17 +235,143 @@ public:
|
||||
}
|
||||
}
|
||||
// Failed. Calculate true residual before giving up
|
||||
Linop.HermOpAndNorm(psi, mmp, d, qq);
|
||||
p = mmp - src;
|
||||
// Linop.HermOpAndNorm(psi, mmp, d, qq);
|
||||
// p = mmp - src;
|
||||
//TrueResidual = sqrt(norm2(p)/ssq);
|
||||
// TrueResidual = 1;
|
||||
|
||||
TrueResidual = sqrt(norm2(p)/ssq);
|
||||
std::cout << GridLogMessage << "ConjugateGradient did NOT converge "<<k<<" / "<< MaxIterations
|
||||
<<" residual "<< std::sqrt(cp / ssq)<< std::endl;
|
||||
SolverTimer.Stop();
|
||||
std::cout << GridLogMessage << "\tPreamble " << PreambleTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\tConstruct " << ConstructTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\tNorm " << NormTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\tAssign " << AssignTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "\tSolver " << SolverTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage << "Solver breakdown "<<std::endl;
|
||||
std::cout << GridLogMessage << "\tMatrix " << MatrixTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogMessage<< "\tLinalg " << LinalgTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogPerformance << "\t\tInner " << InnerTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogPerformance << "\t\tAxpyNorm " << AxpyNormTimer.Elapsed() <<std::endl;
|
||||
std::cout << GridLogPerformance << "\t\tLinearComb " << LinearCombTimer.Elapsed() <<std::endl;
|
||||
|
||||
std::cout << GridLogMessage << "ConjugateGradient did NOT converge "<<k<<" / "<< MaxIterations<< std::endl;
|
||||
|
||||
if (ErrorOnNoConverge) assert(0);
|
||||
if (ErrorOnNoConverge) GRID_ASSERT(0);
|
||||
IterationsToComplete = k;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <class Field>
|
||||
class ConjugateGradientPolynomial : public ConjugateGradient<Field> {
|
||||
public:
|
||||
// Optionally record the CG polynomial
|
||||
std::vector<double> ak;
|
||||
std::vector<double> bk;
|
||||
std::vector<double> poly_p;
|
||||
std::vector<double> poly_r;
|
||||
std::vector<double> poly_Ap;
|
||||
std::vector<double> polynomial;
|
||||
|
||||
public:
|
||||
ConjugateGradientPolynomial(RealD tol, Integer maxit, bool err_on_no_conv = true)
|
||||
: ConjugateGradient<Field>(tol,maxit,err_on_no_conv)
|
||||
{ };
|
||||
void PolyHermOp(LinearOperatorBase<Field> &Linop, const Field &src, Field &psi)
|
||||
{
|
||||
Field tmp(src.Grid());
|
||||
Field AtoN(src.Grid());
|
||||
AtoN = src;
|
||||
psi=AtoN*polynomial[0];
|
||||
for(int n=1;n<polynomial.size();n++){
|
||||
tmp = AtoN;
|
||||
Linop.HermOp(tmp,AtoN);
|
||||
psi = psi + polynomial[n]*AtoN;
|
||||
}
|
||||
}
|
||||
void CGsequenceHermOp(LinearOperatorBase<Field> &Linop, const Field &src, Field &x)
|
||||
{
|
||||
Field Ap(src.Grid());
|
||||
Field r(src.Grid());
|
||||
Field p(src.Grid());
|
||||
p=src;
|
||||
r=src;
|
||||
x=Zero();
|
||||
x.Checkerboard()=src.Checkerboard();
|
||||
for(int k=0;k<ak.size();k++){
|
||||
x = x + ak[k]*p;
|
||||
Linop.HermOp(p,Ap);
|
||||
r = r - ak[k] * Ap;
|
||||
p = r + bk[k] * p;
|
||||
}
|
||||
}
|
||||
void Solve(LinearOperatorBase<Field> &Linop, const Field &src, Field &psi)
|
||||
{
|
||||
psi=Zero();
|
||||
this->operator ()(Linop,src,psi);
|
||||
}
|
||||
virtual void LogBegin(void)
|
||||
{
|
||||
std::cout << "ConjugageGradientPolynomial::LogBegin() "<<std::endl;
|
||||
ak.resize(0);
|
||||
bk.resize(0);
|
||||
polynomial.resize(0);
|
||||
poly_Ap.resize(0);
|
||||
poly_Ap.resize(0);
|
||||
poly_p.resize(1);
|
||||
poly_r.resize(1);
|
||||
poly_p[0]=1.0;
|
||||
poly_r[0]=1.0;
|
||||
};
|
||||
virtual void LogIteration(int k,RealD a,RealD b)
|
||||
{
|
||||
// With zero guess,
|
||||
// p = r = src
|
||||
//
|
||||
// iterate:
|
||||
// x = x + a p
|
||||
// r = r - a A p
|
||||
// p = r + b p
|
||||
//
|
||||
// [0]
|
||||
// r = x
|
||||
// p = x
|
||||
// Ap=0
|
||||
//
|
||||
// [1]
|
||||
// Ap = A x + 0 ==> shift poly P right by 1 and add 0.
|
||||
// x = x + a p ==> add polynomials term by term
|
||||
// r = r - a A p ==> add polynomials term by term
|
||||
// p = r + b p ==> add polynomials term by term
|
||||
//
|
||||
std::cout << "ConjugageGradientPolynomial::LogIteration() "<<k<<std::endl;
|
||||
ak.push_back(a);
|
||||
bk.push_back(b);
|
||||
// Ap= right_shift(p)
|
||||
poly_Ap.resize(k+1);
|
||||
poly_Ap[0]=0.0;
|
||||
for(int i=0;i<k;i++){
|
||||
poly_Ap[i+1]=poly_p[i];
|
||||
}
|
||||
|
||||
// x = x + a p
|
||||
polynomial.resize(k);
|
||||
polynomial[k-1]=0.0;
|
||||
for(int i=0;i<k;i++){
|
||||
polynomial[i] = polynomial[i] + a * poly_p[i];
|
||||
}
|
||||
|
||||
// r = r - a Ap
|
||||
// p = r + b p
|
||||
poly_r.resize(k+1);
|
||||
poly_p.resize(k+1);
|
||||
poly_r[k] = poly_p[k] = 0.0;
|
||||
for(int i=0;i<k+1;i++){
|
||||
poly_r[i] = poly_r[i] - a * poly_Ap[i];
|
||||
poly_p[i] = poly_r[i] + b * poly_p[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
#endif
|
||||
@@ -116,14 +116,14 @@ NAMESPACE_BEGIN(Grid);
|
||||
//Compute double precision rsd and also new RHS vector.
|
||||
Linop_d.HermOp(sol_d, tmp_d);
|
||||
RealD norm = axpy_norm(src_d, -1., tmp_d, src_d_in); //src_d is residual vector
|
||||
|
||||
std::cout<<GridLogMessage<<" rsd norm "<<norm<<std::endl;
|
||||
std::cout<<GridLogMessage<<"MixedPrecisionConjugateGradient: Outer iteration " <<outer_iter<<" residual "<< norm<< " target "<< stop<<std::endl;
|
||||
|
||||
if(norm < OuterLoopNormMult * stop){
|
||||
std::cout<<GridLogMessage<<"MixedPrecisionConjugateGradient: Outer iteration converged on iteration " <<outer_iter <<std::endl;
|
||||
break;
|
||||
}
|
||||
while(norm * inner_tol * inner_tol < stop) inner_tol *= 2; // inner_tol = sqrt(stop/norm) ??
|
||||
while(norm * inner_tol * inner_tol < stop*1.01) inner_tol *= 2; // inner_tol = sqrt(stop/norm) ??
|
||||
|
||||
PrecChangeTimer.Start();
|
||||
precisionChange(src_f, src_d, pc_wk_dp_to_sp);
|
||||
|
||||
@@ -51,6 +51,20 @@ public:
|
||||
LinearFunction<FieldF> *guesser;
|
||||
bool updateResidual;
|
||||
|
||||
// Inner solves on independent partitions of the communicator (--batched-solver-split).
|
||||
// BatchedSplit is the partition MPI layout, empty for no split; BatchedSplitNode selects
|
||||
// one partition per node. Fixed at construction, default from the command line.
|
||||
const Coordinate BatchedSplit;
|
||||
const bool BatchedSplitNode;
|
||||
|
||||
// Copy of Linop_f on the partitions, made once at construction and owned; nullptr when
|
||||
// there is no split or the operator cannot be split. It holds the gauge field as it was at
|
||||
// construction: a solver must not outlive a change of the operator's gauge field. The
|
||||
// outer defect correction uses Linop_d, so a stale copy would cost convergence, not
|
||||
// correctness.
|
||||
SplitOperator<FieldF> *split = nullptr;
|
||||
int Partitions = 1;
|
||||
|
||||
MixedPrecisionConjugateGradientBatched(RealD tol,
|
||||
Integer maxinnerit,
|
||||
Integer maxouterit,
|
||||
@@ -58,10 +72,37 @@ public:
|
||||
GridBase* _sp_grid,
|
||||
LinearOperatorBase<FieldF> &_Linop_f,
|
||||
LinearOperatorBase<FieldD> &_Linop_d,
|
||||
bool _updateResidual=true) :
|
||||
bool _updateResidual=true,
|
||||
const Coordinate &_split=GridDefaultBatchedSolverSplit(),
|
||||
bool _split_node=GridDefaultBatchedSolverSplitNode()) :
|
||||
Linop_f(_Linop_f), Linop_d(_Linop_d),
|
||||
Tolerance(tol), InnerTolerance(tol), MaxInnerIterations(maxinnerit), MaxOuterIterations(maxouterit), MaxPatchupIterations(maxpatchit), SinglePrecGrid(_sp_grid),
|
||||
OuterLoopNormMult(100.), guesser(NULL), updateResidual(_updateResidual) { };
|
||||
OuterLoopNormMult(100.), guesser(NULL), updateResidual(_updateResidual),
|
||||
BatchedSplit(_split), BatchedSplitNode(_split_node)
|
||||
{
|
||||
Coordinate layout = BatchedSolverSplitLayout(SinglePrecGrid,BatchedSplit,BatchedSplitNode,Partitions);
|
||||
if ( Partitions > 1 ) {
|
||||
double t0 = usecond();
|
||||
split = Linop_f.SplitClone(layout);
|
||||
double t1 = usecond();
|
||||
if ( split == nullptr ) {
|
||||
std::cout << GridLogMessage << "MixedPrecisionConjugateGradientBatched: operator cannot be split; serial inner solves" << std::endl;
|
||||
} else {
|
||||
std::cout << GridLogMessage << "MixedPrecisionConjugateGradientBatched: split clone " << (t1-t0)/1.0e6 << " s" << std::endl;
|
||||
HostMemoryReport(SinglePrecGrid,GridLogMessage,"MixedPrecisionConjugateGradientBatched: after split clone");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Owns split
|
||||
MixedPrecisionConjugateGradientBatched(const MixedPrecisionConjugateGradientBatched &) = delete;
|
||||
|
||||
MixedPrecisionConjugateGradientBatched &operator=(const MixedPrecisionConjugateGradientBatched &) = delete;
|
||||
|
||||
~MixedPrecisionConjugateGradientBatched(void)
|
||||
{
|
||||
delete split;
|
||||
}
|
||||
|
||||
void useGuesser(LinearFunction<FieldF> &g){
|
||||
guesser = &g;
|
||||
@@ -77,7 +118,7 @@ public:
|
||||
}
|
||||
|
||||
void operator() (const std::vector<FieldD> &src_d_in, std::vector<FieldD> &sol_d){
|
||||
assert(src_d_in.size() == sol_d.size());
|
||||
GRID_ASSERT(src_d_in.size() == sol_d.size());
|
||||
int NBatch = src_d_in.size();
|
||||
|
||||
std::cout << GridLogMessage << "NBatch = " << NBatch << std::endl;
|
||||
@@ -91,6 +132,8 @@ public:
|
||||
|
||||
GridStopWatch InnerCGtimer;
|
||||
GridStopWatch PrecChangeTimer;
|
||||
GridStopWatch OuterResidualTimer;
|
||||
GridStopWatch PatchupTimer;
|
||||
|
||||
int cb = src_d_in[0].Checkerboard();
|
||||
|
||||
@@ -130,18 +173,27 @@ public:
|
||||
ConjugateGradient<FieldF> CG_f(inner_tol, MaxInnerIterations);
|
||||
CG_f.ErrorOnNoConverge = false;
|
||||
|
||||
SplitTimers splitTimers;
|
||||
if ( split != nullptr && split->Partitions > NBatch ) {
|
||||
std::cout << GridLogWarning << "MixedPrecisionConjugateGradientBatched: " << split->Partitions << " partitions for "
|
||||
<< NBatch << " right-hand sides; the extra partitions only solve zero padding" << std::endl;
|
||||
}
|
||||
|
||||
Integer &outer_iter = TotalOuterIterations; //so it will be equal to the final iteration count
|
||||
|
||||
for(outer_iter = 0; outer_iter < MaxOuterIterations; outer_iter++){
|
||||
std::cout << GridLogMessage << std::endl;
|
||||
std::cout << GridLogMessage << "Outer iteration " << outer_iter << std::endl;
|
||||
HostMemoryReport(DoublePrecGrid,GridLogMessage,"MixedPrecisionConjugateGradientBatched: outer iteration "+std::to_string(outer_iter));
|
||||
|
||||
bool allConverged = true;
|
||||
|
||||
for (int i=0; i<NBatch; i++) {
|
||||
//Compute double precision rsd and also new RHS vector.
|
||||
OuterResidualTimer.Start();
|
||||
Linop_d.HermOp(sol_d[i], tmp_d);
|
||||
norm[i] = axpy_norm(src_d[i], -1., tmp_d, src_d_in[i]); //src_d is residual vector
|
||||
OuterResidualTimer.Stop();
|
||||
|
||||
std::cout<<GridLogMessage<<"MixedPrecisionConjugateGradientBatched: Outer iteration " << outer_iter <<" solve " << i << " residual "<< norm[i] << " target "<< stop[i] <<std::endl;
|
||||
|
||||
@@ -169,12 +221,18 @@ public:
|
||||
(*guesser)(src_f, sol_f);
|
||||
}
|
||||
|
||||
if ( split != nullptr ) {
|
||||
InnerSplitSolves(*split, CG_f, src_f, sol_f, TotalInnerIterations, InnerCGtimer, splitTimers);
|
||||
}
|
||||
|
||||
for (int i=0; i<NBatch; i++) {
|
||||
//Inner CG
|
||||
InnerCGtimer.Start();
|
||||
CG_f(Linop_f, src_f[i], sol_f[i]);
|
||||
InnerCGtimer.Stop();
|
||||
TotalInnerIterations[i] += CG_f.IterationsToComplete;
|
||||
if ( split == nullptr ) {
|
||||
InnerCGtimer.Start();
|
||||
CG_f(Linop_f, src_f[i], sol_f[i]);
|
||||
InnerCGtimer.Stop();
|
||||
TotalInnerIterations[i] += CG_f.IterationsToComplete;
|
||||
}
|
||||
|
||||
//Convert sol back to double and add to double prec solution
|
||||
PrecChangeTimer.Start();
|
||||
@@ -185,16 +243,18 @@ public:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//Final trial CG
|
||||
std::cout << GridLogMessage << std::endl;
|
||||
std::cout<<GridLogMessage<<"MixedPrecisionConjugateGradientBatched: Starting final patch-up double-precision solve"<<std::endl;
|
||||
|
||||
PatchupTimer.Start();
|
||||
for (int i=0; i<NBatch; i++) {
|
||||
ConjugateGradient<FieldD> CG_d(Tolerance, MaxPatchupIterations);
|
||||
CG_d(Linop_d, src_d_in[i], sol_d[i]);
|
||||
TotalFinalStepIterations[i] += CG_d.IterationsToComplete;
|
||||
}
|
||||
PatchupTimer.Stop();
|
||||
|
||||
TotalTimer.Stop();
|
||||
|
||||
@@ -204,8 +264,118 @@ public:
|
||||
}
|
||||
std::cout << GridLogMessage << std::endl;
|
||||
std::cout<<GridLogMessage<<"MixedPrecisionConjugateGradientBatched: Total time " << TotalTimer.Elapsed() << " Precision change " << PrecChangeTimer.Elapsed() << " Inner CG total " << InnerCGtimer.Elapsed() << std::endl;
|
||||
std::cout<<GridLogMessage<<"MixedPrecisionConjugateGradientBatched: Outer residual " << OuterResidualTimer.Elapsed() << " Patch-up " << PatchupTimer.Elapsed() << std::endl;
|
||||
if ( split != nullptr ) {
|
||||
std::cout<<GridLogMessage<<"MixedPrecisionConjugateGradientBatched: Grid_split " << splitTimers.Split.Elapsed() << " (" << splitTimers.SplitCalls << " calls)"
|
||||
<< " Grid_unsplit " << splitTimers.Unsplit.Elapsed() << " (" << splitTimers.UnsplitCalls << " calls)"
|
||||
<< " Staging copies " << splitTimers.Staging.Elapsed()
|
||||
<< " Host to device " << splitTimers.H2D.Elapsed()
|
||||
<< " Iteration count sum " << splitTimers.Reduce.Elapsed() << std::endl;
|
||||
}
|
||||
double accounted = PrecChangeTimer.useconds() + InnerCGtimer.useconds() + OuterResidualTimer.useconds()
|
||||
+ PatchupTimer.useconds() + splitTimers.Split.useconds()
|
||||
+ splitTimers.Unsplit.useconds() + splitTimers.Staging.useconds() + splitTimers.H2D.useconds()
|
||||
+ splitTimers.Reduce.useconds();
|
||||
std::cout<<GridLogMessage<<"MixedPrecisionConjugateGradientBatched: Unaccounted " << (TotalTimer.useconds()-accounted)/1.0e6 << " s" << std::endl;
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// Coarse breakdown of the split path outside the inner CG
|
||||
struct SplitTimers {
|
||||
GridStopWatch Split;
|
||||
GridStopWatch Unsplit;
|
||||
GridStopWatch Staging;
|
||||
GridStopWatch H2D;
|
||||
GridStopWatch Reduce;
|
||||
int SplitCalls = 0;
|
||||
int UnsplitCalls = 0;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// One inner solve per partition, in groups of Partitions right-hand sides. The last
|
||||
// group is zero-padded; a zero source returns at once from CG. Collective.
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
void InnerSplitSolves(SplitOperator<FieldF> &split,
|
||||
ConjugateGradient<FieldF> &CG_f,
|
||||
std::vector<FieldF> &src_f,
|
||||
std::vector<FieldF> &sol_f,
|
||||
std::vector<Integer> &TotalInnerIterations,
|
||||
GridStopWatch &InnerCGtimer,
|
||||
SplitTimers &timers)
|
||||
{
|
||||
int NBatch = src_f.size();
|
||||
int P = split.Partitions;
|
||||
int cb = src_f[0].Checkerboard();
|
||||
|
||||
FieldF s_src(split.FieldGrid);
|
||||
FieldF s_sol(split.FieldGrid);
|
||||
std::vector<FieldF> group_src(P,SinglePrecGrid);
|
||||
std::vector<FieldF> group_sol(P,SinglePrecGrid);
|
||||
std::vector<uint64_t> iters(P);
|
||||
|
||||
for(int g=0;g<NBatch;g+=P){
|
||||
|
||||
// Gather the group; zero-pad past the end of the batch
|
||||
timers.Staging.Start();
|
||||
for(int p=0;p<P;p++){
|
||||
group_src[p].Checkerboard() = cb;
|
||||
group_sol[p].Checkerboard() = cb;
|
||||
if ( g+p < NBatch ) {
|
||||
group_src[p] = src_f[g+p];
|
||||
group_sol[p] = sol_f[g+p];
|
||||
} else {
|
||||
group_src[p] = Zero();
|
||||
group_sol[p] = Zero();
|
||||
}
|
||||
}
|
||||
timers.Staging.Stop();
|
||||
|
||||
// The initial guess (e.g. from the guesser) travels with the source
|
||||
timers.Split.Start();
|
||||
Grid_split(group_src,s_src);
|
||||
Grid_split(group_sol,s_sol);
|
||||
timers.Split.Stop();
|
||||
timers.SplitCalls += 2;
|
||||
|
||||
// Grid_split leaves its output on the host; move it now so the copy is not in the CG time
|
||||
timers.H2D.Start();
|
||||
{
|
||||
autoView(s_src_v, s_src, AcceleratorRead);
|
||||
autoView(s_sol_v, s_sol, AcceleratorRead);
|
||||
}
|
||||
timers.H2D.Stop();
|
||||
|
||||
InnerCGtimer.Start();
|
||||
CG_f(*split.Linop,s_src,s_sol);
|
||||
InnerCGtimer.Stop();
|
||||
|
||||
timers.Unsplit.Start();
|
||||
Grid_unsplit(group_sol,s_sol);
|
||||
timers.Unsplit.Stop();
|
||||
timers.UnsplitCalls += 1;
|
||||
|
||||
// One iteration count per partition, contributed by the partition's rank 0 only
|
||||
timers.Reduce.Start();
|
||||
for(int p=0;p<P;p++){
|
||||
iters[p] = 0;
|
||||
}
|
||||
if ( split.FieldGrid->ThisRank() == 0 ) {
|
||||
iters[split.Partition] = CG_f.IterationsToComplete;
|
||||
}
|
||||
SinglePrecGrid->GlobalSumVector(&iters[0],P);
|
||||
timers.Reduce.Stop();
|
||||
|
||||
// Includes the host to device copy of group_sol left by Grid_unsplit
|
||||
timers.Staging.Start();
|
||||
for(int p=0;p<P && g+p<NBatch;p++){
|
||||
sol_f[g+p] = group_sol[p];
|
||||
TotalInnerIterations[g+p] += iters[p];
|
||||
}
|
||||
timers.Staging.Stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
|
||||
@@ -98,15 +98,15 @@ public:
|
||||
std::vector<RealD> alpha(nshift,1.0);
|
||||
std::vector<Field> ps(nshift,grid);// Search directions
|
||||
|
||||
assert(psi.size()==nshift);
|
||||
assert(mass.size()==nshift);
|
||||
assert(mresidual.size()==nshift);
|
||||
GRID_ASSERT(psi.size()==nshift);
|
||||
GRID_ASSERT(mass.size()==nshift);
|
||||
GRID_ASSERT(mresidual.size()==nshift);
|
||||
|
||||
// dynamic sized arrays on stack; 2d is a pain with vector
|
||||
RealD bs[nshift];
|
||||
RealD rsq[nshift];
|
||||
RealD z[nshift][2];
|
||||
int converged[nshift];
|
||||
// remove dynamic sized arrays on stack; 2d is a pain with vector
|
||||
std::vector<RealD> bs(nshift);
|
||||
std::vector<RealD> rsq(nshift);
|
||||
std::vector<std::array<RealD,2> > z(nshift);
|
||||
std::vector<int> converged(nshift);
|
||||
|
||||
const int primary =0;
|
||||
|
||||
@@ -122,7 +122,7 @@ public:
|
||||
|
||||
// Check lightest mass
|
||||
for(int s=0;s<nshift;s++){
|
||||
assert( mass[s]>= mass[primary] );
|
||||
GRID_ASSERT( mass[s]>= mass[primary] );
|
||||
converged[s]=0;
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ public:
|
||||
for(int s=0;s<nshift;s++){
|
||||
rsq[s] = cp * mresidual[s] * mresidual[s];
|
||||
std::cout<<GridLogMessage<<"ConjugateGradientMultiShift: shift "<<s
|
||||
<<" target resid "<<rsq[s]<<std::endl;
|
||||
<<" target resid^2 "<<rsq[s]<<std::endl;
|
||||
ps[s] = src;
|
||||
}
|
||||
// r and p for primary
|
||||
@@ -338,7 +338,7 @@ public:
|
||||
}
|
||||
// ugly hack
|
||||
std::cout<<GridLogMessage<<"CG multi shift did not converge"<<std::endl;
|
||||
// assert(0);
|
||||
// GRID_ASSERT(0);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -118,16 +118,16 @@ public:
|
||||
FieldF r_f(SinglePrecGrid);
|
||||
FieldD mmp_d(DoublePrecGrid);
|
||||
|
||||
assert(psi_d.size()==nshift);
|
||||
assert(mass.size()==nshift);
|
||||
assert(mresidual.size()==nshift);
|
||||
GRID_ASSERT(psi_d.size()==nshift);
|
||||
GRID_ASSERT(mass.size()==nshift);
|
||||
GRID_ASSERT(mresidual.size()==nshift);
|
||||
|
||||
// dynamic sized arrays on stack; 2d is a pain with vector
|
||||
RealD bs[nshift];
|
||||
RealD rsq[nshift];
|
||||
RealD rsqf[nshift];
|
||||
RealD z[nshift][2];
|
||||
int converged[nshift];
|
||||
std::vector<RealD> bs(nshift);
|
||||
std::vector<RealD> rsq(nshift);
|
||||
std::vector<RealD> rsqf(nshift);
|
||||
std::vector<std::array<RealD,2> > z(nshift);
|
||||
std::vector<int> converged(nshift);
|
||||
|
||||
const int primary =0;
|
||||
|
||||
@@ -141,7 +141,7 @@ public:
|
||||
|
||||
// Check lightest mass
|
||||
for(int s=0;s<nshift;s++){
|
||||
assert( mass[s]>= mass[primary] );
|
||||
GRID_ASSERT( mass[s]>= mass[primary] );
|
||||
converged[s]=0;
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ public:
|
||||
Linop_d.HermOpAndNorm(p_d,mmp_d,d,qq); // mmp = MdagM p d=real(dot(p, mmp)), qq=norm2(mmp)
|
||||
tmp_d = tmp_d - mmp_d;
|
||||
std::cout << " Testing operators match "<<norm2(mmp_d)<<" f "<<norm2(mmp_f)<<" diff "<< norm2(tmp_d)<<std::endl;
|
||||
// assert(norm2(tmp_d)< 1.0e-4);
|
||||
// GRID_ASSERT(norm2(tmp_d)< 1.0e-4);
|
||||
|
||||
axpy(mmp_d,mass[0],p_d,mmp_d);
|
||||
RealD rn = norm2(p_d);
|
||||
@@ -365,7 +365,7 @@ public:
|
||||
|
||||
}
|
||||
std::cout<<GridLogMessage<<"CG multi shift did not converge"<<std::endl;
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -48,12 +48,12 @@ public:
|
||||
|
||||
ShiftedLinop(LinearOperatorBase<Field> &_linop_base, RealD _shift): linop_base(_linop_base), shift(_shift){}
|
||||
|
||||
void OpDiag (const Field &in, Field &out){ assert(0); }
|
||||
void OpDir (const Field &in, Field &out,int dir,int disp){ assert(0); }
|
||||
void OpDirAll (const Field &in, std::vector<Field> &out){ assert(0); }
|
||||
void OpDiag (const Field &in, Field &out){ GRID_ASSERT(0); }
|
||||
void OpDir (const Field &in, Field &out,int dir,int disp){ GRID_ASSERT(0); }
|
||||
void OpDirAll (const Field &in, std::vector<Field> &out){ GRID_ASSERT(0); }
|
||||
|
||||
void Op (const Field &in, Field &out){ assert(0); }
|
||||
void AdjOp (const Field &in, Field &out){ assert(0); }
|
||||
void Op (const Field &in, Field &out){ GRID_ASSERT(0); }
|
||||
void AdjOp (const Field &in, Field &out){ GRID_ASSERT(0); }
|
||||
|
||||
void HermOp(const Field &in, Field &out){
|
||||
linop_base.HermOp(in, out);
|
||||
@@ -151,16 +151,16 @@ public:
|
||||
FieldD r_d(DoublePrecGrid);
|
||||
FieldD mmp_d(DoublePrecGrid);
|
||||
|
||||
assert(psi_d.size()==nshift);
|
||||
assert(mass.size()==nshift);
|
||||
assert(mresidual.size()==nshift);
|
||||
GRID_ASSERT(psi_d.size()==nshift);
|
||||
GRID_ASSERT(mass.size()==nshift);
|
||||
GRID_ASSERT(mresidual.size()==nshift);
|
||||
|
||||
// dynamic sized arrays on stack; 2d is a pain with vector
|
||||
RealD bs[nshift];
|
||||
RealD rsq[nshift];
|
||||
RealD rsqf[nshift];
|
||||
RealD z[nshift][2];
|
||||
int converged[nshift];
|
||||
std::vector<RealD> bs(nshift);
|
||||
std::vector<RealD> rsq(nshift);
|
||||
std::vector<RealD> rsqf(nshift);
|
||||
std::vector<std::array<RealD,2> > z(nshift);
|
||||
std::vector<int> converged(nshift);
|
||||
|
||||
const int primary =0;
|
||||
|
||||
@@ -174,7 +174,7 @@ public:
|
||||
|
||||
// Check lightest mass
|
||||
for(int s=0;s<nshift;s++){
|
||||
assert( mass[s]>= mass[primary] );
|
||||
GRID_ASSERT( mass[s]>= mass[primary] );
|
||||
converged[s]=0;
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ public:
|
||||
Linop_d.HermOpAndNorm(p_d,mmp_d,d,qq); // mmp = MdagM p d=real(dot(p, mmp)), qq=norm2(mmp)
|
||||
tmp_d = tmp_d - mmp_d;
|
||||
std::cout << " Testing operators match "<<norm2(mmp_d)<<" f "<<norm2(mmp_f)<<" diff "<< norm2(tmp_d)<<std::endl;
|
||||
assert(norm2(tmp_d)< 1.0);
|
||||
GRID_ASSERT(norm2(tmp_d)< 1.0);
|
||||
|
||||
axpy(mmp_d,mass[0],p_d,mmp_d);
|
||||
RealD rn = norm2(p_d);
|
||||
@@ -408,7 +408,7 @@ public:
|
||||
|
||||
}
|
||||
std::cout<<GridLogMessage<<"CG multi shift did not converge"<<std::endl;
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ template<class FieldD,class FieldF,
|
||||
typename std::enable_if< getPrecision<FieldF>::value == 1, int>::type = 0>
|
||||
class ConjugateGradientReliableUpdate : public LinearFunction<FieldD> {
|
||||
public:
|
||||
bool ErrorOnNoConverge; // throw an assert when the CG fails to converge.
|
||||
bool ErrorOnNoConverge; // throw an GRID_ASSERT when the CG fails to converge.
|
||||
// Defaults true.
|
||||
RealD Tolerance;
|
||||
Integer MaxIterations;
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
DoFinalCleanup(true),
|
||||
Linop_fallback(NULL)
|
||||
{
|
||||
assert(Delta > 0. && Delta < 1. && "Expect 0 < Delta < 1");
|
||||
GRID_ASSERT(Delta > 0. && Delta < 1. && "Expect 0 < Delta < 1");
|
||||
};
|
||||
|
||||
void setFallbackLinop(LinearOperatorBase<FieldF> &_Linop_fallback, const RealD _fallback_transition_tol){
|
||||
@@ -90,7 +90,7 @@ public:
|
||||
|
||||
// Initial residual computation & set up
|
||||
RealD guess = norm2(psi);
|
||||
assert(std::isnan(guess) == 0);
|
||||
GRID_ASSERT(std::isnan(guess) == 0);
|
||||
|
||||
Linop_d.HermOpAndNorm(psi, mmp, d, b);
|
||||
|
||||
@@ -217,7 +217,7 @@ public:
|
||||
CG(Linop_d,src,psi);
|
||||
IterationsToCleanup = CG.IterationsToComplete;
|
||||
}
|
||||
else if (ErrorOnNoConverge) assert(true_residual / Tolerance < 10000.0);
|
||||
else if (ErrorOnNoConverge) GRID_ASSERT(true_residual / Tolerance < 10000.0);
|
||||
|
||||
std::cout << GridLogMessage << "ConjugateGradientReliableUpdate complete.\n";
|
||||
return;
|
||||
@@ -263,7 +263,7 @@ public:
|
||||
std::cout << GridLogMessage << "ConjugateGradientReliableUpdate did NOT converge"
|
||||
<< std::endl;
|
||||
|
||||
if (ErrorOnNoConverge) assert(0);
|
||||
if (ErrorOnNoConverge) GRID_ASSERT(0);
|
||||
IterationsToComplete = k;
|
||||
ReliableUpdatesPerformed = l;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public:
|
||||
}
|
||||
|
||||
std::cout<<GridLogMessage<<"ConjugateResidual did NOT converge"<<std::endl;
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
};
|
||||
NAMESPACE_END(Grid);
|
||||
|
||||
@@ -36,7 +36,7 @@ class FlexibleCommunicationAvoidingGeneralisedMinimalResidual : public OperatorF
|
||||
public:
|
||||
using OperatorFunction<Field>::operator();
|
||||
|
||||
bool ErrorOnNoConverge; // Throw an assert when FCAGMRES fails to converge,
|
||||
bool ErrorOnNoConverge; // Throw an GRID_ASSERT when FCAGMRES fails to converge,
|
||||
// defaults to true
|
||||
|
||||
RealD Tolerance;
|
||||
@@ -87,7 +87,7 @@ class FlexibleCommunicationAvoidingGeneralisedMinimalResidual : public OperatorF
|
||||
conformable(psi, src);
|
||||
|
||||
RealD guess = norm2(psi);
|
||||
assert(std::isnan(guess) == 0);
|
||||
GRID_ASSERT(std::isnan(guess) == 0);
|
||||
|
||||
RealD cp;
|
||||
RealD ssq = norm2(src);
|
||||
@@ -144,7 +144,7 @@ class FlexibleCommunicationAvoidingGeneralisedMinimalResidual : public OperatorF
|
||||
std::cout << GridLogMessage << "FlexibleCommunicationAvoidingGeneralisedMinimalResidual did NOT converge" << std::endl;
|
||||
|
||||
if (ErrorOnNoConverge)
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
|
||||
RealD outerLoopBody(LinearOperatorBase<Field> &LinOp, const Field &src, Field &psi, RealD rsq) {
|
||||
@@ -191,7 +191,7 @@ class FlexibleCommunicationAvoidingGeneralisedMinimalResidual : public OperatorF
|
||||
}
|
||||
}
|
||||
|
||||
assert(0); // Never reached
|
||||
GRID_ASSERT(0); // Never reached
|
||||
return cp;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class FlexibleGeneralisedMinimalResidual : public OperatorFunction<Field> {
|
||||
public:
|
||||
using OperatorFunction<Field>::operator();
|
||||
|
||||
bool ErrorOnNoConverge; // Throw an assert when FGMRES fails to converge,
|
||||
bool ErrorOnNoConverge; // Throw an GRID_ASSERT when FGMRES fails to converge,
|
||||
// defaults to true
|
||||
|
||||
RealD Tolerance;
|
||||
@@ -85,7 +85,7 @@ class FlexibleGeneralisedMinimalResidual : public OperatorFunction<Field> {
|
||||
conformable(psi, src);
|
||||
|
||||
RealD guess = norm2(psi);
|
||||
assert(std::isnan(guess) == 0);
|
||||
GRID_ASSERT(std::isnan(guess) == 0);
|
||||
|
||||
RealD cp;
|
||||
RealD ssq = norm2(src);
|
||||
@@ -142,7 +142,7 @@ class FlexibleGeneralisedMinimalResidual : public OperatorFunction<Field> {
|
||||
std::cout << GridLogMessage << "FlexibleGeneralisedMinimalResidual did NOT converge" << std::endl;
|
||||
|
||||
if (ErrorOnNoConverge)
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
|
||||
RealD outerLoopBody(LinearOperatorBase<Field> &LinOp, const Field &src, Field &psi, RealD rsq) {
|
||||
@@ -189,7 +189,7 @@ class FlexibleGeneralisedMinimalResidual : public OperatorFunction<Field> {
|
||||
}
|
||||
}
|
||||
|
||||
assert(0); // Never reached
|
||||
GRID_ASSERT(0); // Never reached
|
||||
return cp;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./Grid/algorithms/iterative/GCRCoefficients.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution
|
||||
directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Recorded GCR coefficients: per-step means over calls of the step length
|
||||
// a_k and the orthogonalisation coefficients b_kj (already scaled and
|
||||
// signed as applied: p_{k+1} = r + sum_j b_kj p_{k-j}).
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
struct GCRCoefficients {
|
||||
int mmax = 0;
|
||||
// Every recorded call is kept: calls[c] = list of (a_k, [b_kj]) per step.
|
||||
// A(k)/B(k,j) return the coefficients of the SELECTED call: by default the
|
||||
// last complete one. Selection "mean" averages coefficients over calls --
|
||||
// kept for comparison only: the mean of the coefficients of a nonlinear
|
||||
// recurrence is not the mean of the polynomials, and in practice (Frontier
|
||||
// M3, 2026-08-26) it was worse than every individual call.
|
||||
enum Select { Last=0, First=1, Index=2, Mean=3 };
|
||||
Select select = Last;
|
||||
int index = 0;
|
||||
typedef std::vector<std::pair<ComplexD,std::vector<ComplexD> > > Call;
|
||||
std::vector<Call> calls;
|
||||
Call current;
|
||||
void RecordA(int k, ComplexD a){
|
||||
if ( k==0 && current.size() ) { calls.push_back(current); current.clear(); }
|
||||
if ( (int)current.size() <= k ) current.resize(k+1);
|
||||
current[k].first = a;
|
||||
}
|
||||
void RecordB(int k, const std::vector<ComplexD> &b){
|
||||
if ( (int)current.size() <= k ) current.resize(k+1);
|
||||
current[k].second = b;
|
||||
}
|
||||
void Flush(void){ if ( current.size() ) { calls.push_back(current); current.clear(); } }
|
||||
int Calls(void) const { return calls.size() + (current.size() ? 1 : 0); }
|
||||
const Call & Chosen(void) const {
|
||||
GRID_ASSERT( calls.size() || current.size() );
|
||||
if ( calls.empty() ) return current;
|
||||
if ( select==First ) return calls.front();
|
||||
if ( select==Index ) { GRID_ASSERT(index>=0 && index<(int)calls.size()); return calls[index]; }
|
||||
return calls.back();
|
||||
}
|
||||
int Steps(void) const { return select==Mean ? MeanSteps() : Chosen().size(); }
|
||||
int NB(int k) const { return select==Mean ? MeanNB(k) : Chosen()[k].second.size(); }
|
||||
ComplexD A(int k) const { return select==Mean ? MeanA(k) : Chosen()[k].first; }
|
||||
ComplexD B(int k,int j) const { return select==Mean ? MeanB(k,j) : Chosen()[k].second[j]; }
|
||||
// mean over calls (comparison only)
|
||||
int MeanSteps(void) const { int m=0; for(auto &c:calls) m = std::max(m,(int)c.size()); return m; }
|
||||
int MeanNB(int k) const { int m=0; for(auto &c:calls) if(k<(int)c.size()) m = std::max(m,(int)c[k].second.size()); return m; }
|
||||
ComplexD MeanA(int k) const { ComplexD s(0.0); int n=0; for(auto &c:calls) if(k<(int)c.size()){ s+=c[k].first; n++; } return s/(double)n; }
|
||||
ComplexD MeanB(int k,int j) const { ComplexD s(0.0); int n=0; for(auto &c:calls) if(k<(int)c.size() && j<(int)c[k].second.size()){ s+=c[k].second[j]; n++; } return s/(double)n; }
|
||||
void Report(const std::string &name) const {
|
||||
const char *sel[4]={"last","first","index","mean"};
|
||||
std::cout << GridLogMessage << "GCRCoefficients " << name << ": " << Calls() << " calls, " << Steps() << " steps, mmax " << mmax
|
||||
<< ", selection " << sel[select] << std::endl;
|
||||
for(int k=0;k<Steps();k++){
|
||||
std::cout << GridLogMessage << " step " << k << " a=(" << real(A(k)) << "," << imag(A(k)) << ")";
|
||||
for(int j=0;j<NB(k);j++) std::cout << " b[" << j << "]=(" << real(B(k,j)) << "," << imag(B(k,j)) << ")";
|
||||
// spread of a_k across calls: how different the individual polynomials are
|
||||
if ( calls.size()>1 ) {
|
||||
RealD lo=1e300, hi=0; for(auto &c:calls) if(k<(int)c.size()){ RealD x=real(c[k].first), y=imag(c[k].first); RealD m=std::sqrt(x*x+y*y); lo=std::min(lo,m); hi=std::max(hi,m); }
|
||||
std::cout << " |a| over calls [" << lo << "," << hi << "]";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -36,7 +36,7 @@ class GeneralisedMinimalResidual : public OperatorFunction<Field> {
|
||||
public:
|
||||
using OperatorFunction<Field>::operator();
|
||||
|
||||
bool ErrorOnNoConverge; // Throw an assert when GMRES fails to converge,
|
||||
bool ErrorOnNoConverge; // Throw an GRID_ASSERT when GMRES fails to converge,
|
||||
// defaults to true
|
||||
|
||||
RealD Tolerance;
|
||||
@@ -80,7 +80,7 @@ class GeneralisedMinimalResidual : public OperatorFunction<Field> {
|
||||
conformable(psi, src);
|
||||
|
||||
RealD guess = norm2(psi);
|
||||
assert(std::isnan(guess) == 0);
|
||||
GRID_ASSERT(std::isnan(guess) == 0);
|
||||
|
||||
RealD cp;
|
||||
RealD ssq = norm2(src);
|
||||
@@ -135,7 +135,7 @@ class GeneralisedMinimalResidual : public OperatorFunction<Field> {
|
||||
std::cout << GridLogMessage << "GeneralisedMinimalResidual did NOT converge" << std::endl;
|
||||
|
||||
if (ErrorOnNoConverge)
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
|
||||
RealD outerLoopBody(LinearOperatorBase<Field> &LinOp, const Field &src, Field &psi, RealD rsq) {
|
||||
@@ -181,7 +181,7 @@ class GeneralisedMinimalResidual : public OperatorFunction<Field> {
|
||||
}
|
||||
}
|
||||
|
||||
assert(0); // Never reached
|
||||
GRID_ASSERT(0); // Never reached
|
||||
return cp;
|
||||
}
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ public:
|
||||
eresid(_eresid), MaxIter(_MaxIter),
|
||||
diagonalisation(_diagonalisation),split_test(0),
|
||||
Nevec_acc(_Nu)
|
||||
{ assert( (Nk%Nu==0) && (Nm%Nu==0) ); };
|
||||
{ GRID_ASSERT( (Nk%Nu==0) && (Nm%Nu==0) ); };
|
||||
|
||||
////////////////////////////////
|
||||
// Helpers
|
||||
@@ -206,7 +206,7 @@ public:
|
||||
Glog<<"orthogonalize after: "<<j<<" of "<<k<<" "<< ip <<std::endl;
|
||||
}
|
||||
}
|
||||
assert(normalize(w,if_print) != 0);
|
||||
GRID_ASSERT(normalize(w,if_print) != 0);
|
||||
}
|
||||
void reorthogonalize(Field& w, std::vector<Field>& evec, int k)
|
||||
{
|
||||
@@ -225,7 +225,7 @@ public:
|
||||
w[i] = w[i] - ip * evec[j];
|
||||
}}
|
||||
for(int i=0; i<_Nu; ++i)
|
||||
assert(normalize(w[i],if_print) !=0);
|
||||
GRID_ASSERT(normalize(w[i],if_print) !=0);
|
||||
}
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ public:
|
||||
const uint64_t sites = grid->lSites();
|
||||
|
||||
int Nbatch = R/Nevec_acc;
|
||||
assert( R%Nevec_acc == 0 );
|
||||
GRID_ASSERT( R%Nevec_acc == 0 );
|
||||
// Glog << "nBatch, Nevec_acc, R, Nu = "
|
||||
// << Nbatch << "," << Nevec_acc << "," << R << "," << Nu << std::endl;
|
||||
|
||||
@@ -302,7 +302,7 @@ public:
|
||||
}
|
||||
}
|
||||
for (int i=0; i<Nu; ++i) {
|
||||
assert(normalize(w[i],do_print)!=0);
|
||||
GRID_ASSERT(normalize(w[i],do_print)!=0);
|
||||
}
|
||||
|
||||
Glog << "cuBLAS Zgemm done"<< std::endl;
|
||||
@@ -374,8 +374,8 @@ cudaStat = cudaMallocManaged((void **)&evec_acc, Nevec_acc*sites*12*sizeof(CUDA_
|
||||
{
|
||||
std::string fname = std::string(cname+"::calc_irbl()");
|
||||
GridBase *grid = evec[0].Grid();
|
||||
assert(grid == src[0].Grid());
|
||||
assert( Nu = src.size() );
|
||||
GRID_ASSERT(grid == src[0].Grid());
|
||||
GRID_ASSERT( Nu = src.size() );
|
||||
|
||||
Glog << std::string(74,'*') << std::endl;
|
||||
Glog << fname + " starting iteration 0 / "<< MaxIter<< std::endl;
|
||||
@@ -396,7 +396,7 @@ cudaStat = cudaMallocManaged((void **)&evec_acc, Nevec_acc*sites*12*sizeof(CUDA_
|
||||
}
|
||||
Glog << std::string(74,'*') << std::endl;
|
||||
|
||||
assert(Nm == evec.size() && Nm == eval.size());
|
||||
GRID_ASSERT(Nm == evec.size() && Nm == eval.size());
|
||||
|
||||
std::vector<std::vector<ComplexD>> lmd(Nu,std::vector<ComplexD>(Nm,0.0));
|
||||
std::vector<std::vector<ComplexD>> lme(Nu,std::vector<ComplexD>(Nm,0.0));
|
||||
@@ -579,8 +579,8 @@ cudaStat = cudaMallocManaged((void **)&evec_acc, Nevec_acc*sites*12*sizeof(CUDA_
|
||||
{
|
||||
std::string fname = std::string(cname+"::calc_rbl()");
|
||||
GridBase *grid = evec[0].Grid();
|
||||
assert(grid == src[0].Grid());
|
||||
assert( Nu = src.size() );
|
||||
GRID_ASSERT(grid == src[0].Grid());
|
||||
GRID_ASSERT( Nu = src.size() );
|
||||
|
||||
int Np = (Nm-Nk);
|
||||
if (Np > 0 && MaxIter > 1) Np /= MaxIter;
|
||||
@@ -607,7 +607,7 @@ cudaStat = cudaMallocManaged((void **)&evec_acc, Nevec_acc*sites*12*sizeof(CUDA_
|
||||
}
|
||||
Glog << std::string(74,'*') << std::endl;
|
||||
|
||||
assert(Nm == evec.size() && Nm == eval.size());
|
||||
GRID_ASSERT(Nm == evec.size() && Nm == eval.size());
|
||||
|
||||
std::vector<std::vector<ComplexD>> lmd(Nu,std::vector<ComplexD>(Nm,0.0));
|
||||
std::vector<std::vector<ComplexD>> lme(Nu,std::vector<ComplexD>(Nm,0.0));
|
||||
@@ -785,7 +785,7 @@ private:
|
||||
|
||||
int Nu = w.size();
|
||||
int Nm = evec.size();
|
||||
assert( b < Nm/Nu );
|
||||
GRID_ASSERT( b < Nm/Nu );
|
||||
// GridCartesian *grid = evec[0]._grid;
|
||||
|
||||
// converts block index to full indicies for an interval [L,R)
|
||||
@@ -796,7 +796,7 @@ private:
|
||||
|
||||
Glog << "Using split grid"<< std::endl;
|
||||
// LatticeGaugeField s_Umu(SGrid);
|
||||
assert((Nu%mrhs)==0);
|
||||
GRID_ASSERT((Nu%mrhs)==0);
|
||||
std::vector<Field> in(mrhs,f_grid);
|
||||
|
||||
Field s_in(sf_grid);
|
||||
@@ -906,7 +906,7 @@ if(split_test){
|
||||
|
||||
for (int u=0; u<Nu; ++u) {
|
||||
// Glog << "norm2(w[" << u << "])= "<< norm2(w[u]) << std::endl;
|
||||
assert (!isnan(norm2(w[u])));
|
||||
GRID_ASSERT (!isnan(norm2(w[u])));
|
||||
for (int k=L+u; k<R; ++k) {
|
||||
Glog <<" In block "<< b << "," <<" beta[" << u << "," << k-L << "] = " << lme[u][k] << std::endl;
|
||||
}
|
||||
@@ -929,8 +929,8 @@ if(split_test){
|
||||
Eigen::MatrixXcd & Qt, // Nm x Nm
|
||||
GridBase *grid)
|
||||
{
|
||||
assert( Nk%Nu == 0 && Nm%Nu == 0 );
|
||||
assert( Nk <= Nm );
|
||||
GRID_ASSERT( Nk%Nu == 0 && Nm%Nu == 0 );
|
||||
GRID_ASSERT( Nk <= Nm );
|
||||
Eigen::MatrixXcd BlockTriDiag = Eigen::MatrixXcd::Zero(Nk,Nk);
|
||||
|
||||
for ( int u=0; u<Nu; ++u ) {
|
||||
@@ -970,8 +970,8 @@ if(split_test){
|
||||
GridBase *grid)
|
||||
{
|
||||
Glog << "diagonalize_lapack: Nu= "<<Nu<<" Nk= "<<Nk<<" Nm= "<<std::endl;
|
||||
assert( Nk%Nu == 0 && Nm%Nu == 0 );
|
||||
assert( Nk <= Nm );
|
||||
GRID_ASSERT( Nk%Nu == 0 && Nm%Nu == 0 );
|
||||
GRID_ASSERT( Nk <= Nm );
|
||||
Eigen::MatrixXcd BlockTriDiag = Eigen::MatrixXcd::Zero(Nk,Nk);
|
||||
|
||||
for ( int u=0; u<Nu; ++u ) {
|
||||
@@ -1119,7 +1119,7 @@ if (1){
|
||||
diagonalize_lapack(eval,lmd,lme,Nu,Nk,Nm,Qt,grid);
|
||||
#endif
|
||||
} else {
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1131,8 +1131,8 @@ if (1){
|
||||
Eigen::MatrixXcd& M)
|
||||
{
|
||||
//Glog << "unpackHermitBlockTriDiagMatToEigen() begin" << '\n';
|
||||
assert( Nk%Nu == 0 && Nm%Nu == 0 );
|
||||
assert( Nk <= Nm );
|
||||
GRID_ASSERT( Nk%Nu == 0 && Nm%Nu == 0 );
|
||||
GRID_ASSERT( Nk <= Nm );
|
||||
M = Eigen::MatrixXcd::Zero(Nk,Nk);
|
||||
|
||||
// rearrange
|
||||
@@ -1159,8 +1159,8 @@ if (1){
|
||||
Eigen::MatrixXcd& M)
|
||||
{
|
||||
//Glog << "packHermitBlockTriDiagMatfromEigen() begin" << '\n';
|
||||
assert( Nk%Nu == 0 && Nm%Nu == 0 );
|
||||
assert( Nk <= Nm );
|
||||
GRID_ASSERT( Nk%Nu == 0 && Nm%Nu == 0 );
|
||||
GRID_ASSERT( Nk <= Nm );
|
||||
|
||||
// rearrange
|
||||
for ( int u=0; u<Nu; ++u ) {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -79,14 +79,16 @@ template<class Field> class ImplicitlyRestartedLanczosHermOpTester : public Imp
|
||||
RealD vv = norm2(v) / ::pow(evalMaxApprox,2.0);
|
||||
|
||||
std::cout.precision(13);
|
||||
std::cout<<GridLogIRL << "[" << std::setw(3)<<j<<"] "
|
||||
<<"eval = "<<std::setw(25)<< eval << " (" << eval_poly << ")"
|
||||
<<" |H B[i] - eval[i]B[i]|^2 / evalMaxApprox^2 " << std::setw(25) << vv
|
||||
<<std::endl;
|
||||
|
||||
int conv=0;
|
||||
if( (vv<eresid*eresid) ) conv = 1;
|
||||
|
||||
std::cout<<GridLogIRL << "[" << std::setw(3)<<j<<"] "
|
||||
<<"eval = "<<std::setw(25)<< eval << " (" << eval_poly << ")"
|
||||
<<" |H B[i] - eval[i]B[i]|^2 / evalMaxApprox^2 " << std::setw(25) << vv
|
||||
<<" target " << eresid*eresid << " conv " <<conv
|
||||
<<std::endl;
|
||||
|
||||
return conv;
|
||||
}
|
||||
};
|
||||
@@ -209,7 +211,7 @@ until convergence
|
||||
void calc(std::vector<RealD>& eval, std::vector<Field>& evec, const Field& src, int& Nconv, bool reverse=false)
|
||||
{
|
||||
GridBase *grid = src.Grid();
|
||||
assert(grid == evec[0].Grid());
|
||||
GRID_ASSERT(grid == evec[0].Grid());
|
||||
|
||||
// GridLogIRL.TimingMode(1);
|
||||
std::cout << GridLogIRL <<"**************************************************************************"<< std::endl;
|
||||
@@ -229,7 +231,7 @@ until convergence
|
||||
}
|
||||
std::cout << GridLogIRL <<"**************************************************************************"<< std::endl;
|
||||
|
||||
assert(Nm <= evec.size() && Nm <= eval.size());
|
||||
GRID_ASSERT(Nm <= evec.size() && Nm <= eval.size());
|
||||
|
||||
// quickly get an idea of the largest eigenvalue to more properly normalize the residuum
|
||||
RealD evalMaxApprox = 0.0;
|
||||
@@ -243,9 +245,10 @@ until convergence
|
||||
_HermOp(src_n,tmp);
|
||||
// std::cout << GridLogMessage<< tmp<<std::endl; exit(0);
|
||||
// std::cout << GridLogIRL << " _HermOp " << norm2(tmp) << std::endl;
|
||||
RealD vnum = real(innerProduct(src_n,tmp)); // HermOp.
|
||||
// RealD vnum = real(innerProduct(src_n,tmp)); // HermOp.
|
||||
RealD vnum = real(innerProduct(tmp,tmp)); // HermOp^2.
|
||||
RealD vden = norm2(src_n);
|
||||
RealD na = vnum/vden;
|
||||
RealD na = std::sqrt(vnum/vden);
|
||||
if (fabs(evalMaxApprox/na - 1.0) < 0.0001)
|
||||
i=_MAX_ITER_IRL_MEVAPP_;
|
||||
evalMaxApprox = na;
|
||||
@@ -253,6 +256,7 @@ until convergence
|
||||
src_n = tmp;
|
||||
}
|
||||
}
|
||||
std::cout << GridLogIRL << " Final evalMaxApprox " << evalMaxApprox << std::endl;
|
||||
|
||||
std::vector<RealD> lme(Nm);
|
||||
std::vector<RealD> lme2(Nm);
|
||||
@@ -333,7 +337,7 @@ until convergence
|
||||
}
|
||||
std::cout<<GridLogIRL <<"QR decomposed "<<std::endl;
|
||||
|
||||
assert(k2<Nm); assert(k2<Nm); assert(k1>0);
|
||||
GRID_ASSERT(k2<Nm); GRID_ASSERT(k2<Nm); GRID_ASSERT(k1>0);
|
||||
|
||||
basisRotate(evec,Qt,k1-1,k2+1,0,Nm,Nm); /// big constraint on the basis
|
||||
std::cout<<GridLogIRL <<"basisRotated by Qt *"<<k1-1<<","<<k2+1<<")"<<std::endl;
|
||||
@@ -457,15 +461,15 @@ until convergence
|
||||
std::vector<Field>& evec,
|
||||
Field& w,int Nm,int k)
|
||||
{
|
||||
std::cout<<GridLogIRL << "Lanczos step " <<k<<std::endl;
|
||||
std::cout<<GridLogDebug << "Lanczos step " <<k<<std::endl;
|
||||
const RealD tiny = 1.0e-20;
|
||||
assert( k< Nm );
|
||||
GRID_ASSERT( k< Nm );
|
||||
|
||||
GridStopWatch gsw_op,gsw_o;
|
||||
|
||||
Field& evec_k = evec[k];
|
||||
|
||||
_PolyOp(evec_k,w); std::cout<<GridLogIRL << "PolyOp" <<std::endl;
|
||||
_PolyOp(evec_k,w); std::cout<<GridLogDebug << "PolyOp" <<std::endl;
|
||||
|
||||
if(k>0) w -= lme[k-1] * evec[k-1];
|
||||
|
||||
@@ -480,18 +484,18 @@ until convergence
|
||||
lme[k] = beta;
|
||||
|
||||
if ( (k>0) && ( (k % orth_period) == 0 )) {
|
||||
std::cout<<GridLogIRL << "Orthogonalising " <<k<<std::endl;
|
||||
std::cout<<GridLogDebug << "Orthogonalising " <<k<<std::endl;
|
||||
orthogonalize(w,evec,k); // orthonormalise
|
||||
std::cout<<GridLogIRL << "Orthogonalised " <<k<<std::endl;
|
||||
std::cout<<GridLogDebug << "Orthogonalised " <<k<<std::endl;
|
||||
}
|
||||
|
||||
if(k < Nm-1) evec[k+1] = w;
|
||||
|
||||
std::cout<<GridLogIRL << "alpha[" << k << "] = " << zalph << " beta[" << k << "] = "<<beta<<std::endl;
|
||||
std::cout<<GridLogIRL << "Lanczos step alpha[" << k << "] = " << zalph << " beta[" << k << "] = "<<beta<<std::endl;
|
||||
if ( beta < tiny )
|
||||
std::cout<<GridLogIRL << " beta is tiny "<<beta<<std::endl;
|
||||
|
||||
std::cout<<GridLogIRL << "Lanczos step complete " <<k<<std::endl;
|
||||
std::cout<<GridLogDebug << "Lanczos step complete " <<k<<std::endl;
|
||||
}
|
||||
|
||||
void diagonalize_Eigen(std::vector<RealD>& lmd, std::vector<RealD>& lme,
|
||||
@@ -593,7 +597,7 @@ until convergence
|
||||
} else if ( diagonalisation == IRLdiagonaliseWithEigen ) {
|
||||
diagonalize_Eigen(lmd,lme,Nk,Nm,Qt,grid);
|
||||
} else {
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,7 +687,7 @@ void diagonalize_lapack(std::vector<RealD>& lmd,
|
||||
}
|
||||
}
|
||||
#else
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ public:
|
||||
ProjectedHermOp(LinearOperatorBase<FineField>& linop, std::vector<FineField> & _subspace) :
|
||||
_Linop(linop), subspace(_subspace)
|
||||
{
|
||||
assert(subspace.size() >0);
|
||||
GRID_ASSERT(subspace.size() >0);
|
||||
};
|
||||
|
||||
void operator()(const CoarseField& in, CoarseField& out) {
|
||||
@@ -346,12 +346,12 @@ public:
|
||||
|
||||
void testFine(RealD resid)
|
||||
{
|
||||
assert(evals_fine.size() == nbasis);
|
||||
assert(subspace.size() == nbasis);
|
||||
GRID_ASSERT(evals_fine.size() == nbasis);
|
||||
GRID_ASSERT(subspace.size() == nbasis);
|
||||
PlainHermOp<FineField> Op(_FineOp);
|
||||
ImplicitlyRestartedLanczosHermOpTester<FineField> SimpleTester(Op);
|
||||
for(int k=0;k<nbasis;k++){
|
||||
assert(SimpleTester.ReconstructEval(k,resid,subspace[k],evals_fine[k],1.0)==1);
|
||||
GRID_ASSERT(SimpleTester.ReconstructEval(k,resid,subspace[k],evals_fine[k],1.0)==1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,8 +359,8 @@ public:
|
||||
//hence the smoother can be tuned after running the coarse Lanczos by using a different smoother here
|
||||
void testCoarse(RealD resid,ChebyParams cheby_smooth,RealD relax)
|
||||
{
|
||||
assert(evals_fine.size() == nbasis);
|
||||
assert(subspace.size() == nbasis);
|
||||
GRID_ASSERT(evals_fine.size() == nbasis);
|
||||
GRID_ASSERT(subspace.size() == nbasis);
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// create a smoother and see if we can get a cheap convergence test and smooth inside the IRL
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -380,7 +380,7 @@ public:
|
||||
void calcFine(ChebyParams cheby_parms,int Nstop,int Nk,int Nm,RealD resid,
|
||||
RealD MaxIt, RealD betastp, int MinRes)
|
||||
{
|
||||
assert(nbasis<=Nm);
|
||||
GRID_ASSERT(nbasis<=Nm);
|
||||
Chebyshev<FineField> Cheby(cheby_parms);
|
||||
FunctionHermOp<FineField> ChebyOp(Cheby,_FineOp);
|
||||
PlainHermOp<FineField> Op(_FineOp);
|
||||
@@ -400,8 +400,8 @@ public:
|
||||
IRL.calc(evals_fine,subspace,src,Nconv,false);
|
||||
|
||||
// Shrink down to number saved
|
||||
assert(Nstop>=nbasis);
|
||||
assert(Nconv>=nbasis);
|
||||
GRID_ASSERT(Nstop>=nbasis);
|
||||
GRID_ASSERT(Nconv>=nbasis);
|
||||
evals_fine.resize(nbasis);
|
||||
subspace.resize(nbasis,_FineGrid);
|
||||
}
|
||||
@@ -433,7 +433,7 @@ public:
|
||||
ImplicitlyRestartedLanczos<CoarseField> IRL(ChebyOp,ChebyOp,ChebySmoothTester,Nstop,Nk,Nm,resid,MaxIt,betastp,MinRes);
|
||||
int Nconv=0;
|
||||
IRL.calc(evals_coarse,evec_coarse,src,Nconv,false);
|
||||
assert(Nconv>=Nstop);
|
||||
GRID_ASSERT(Nconv>=Nstop);
|
||||
evals_coarse.resize(Nstop);
|
||||
evec_coarse.resize (Nstop,_CoarseGrid);
|
||||
for (int i=0;i<Nstop;i++){
|
||||
|
||||
@@ -35,7 +35,7 @@ template<class Field> class MinimalResidual : public OperatorFunction<Field> {
|
||||
public:
|
||||
using OperatorFunction<Field>::operator();
|
||||
|
||||
bool ErrorOnNoConverge; // throw an assert when the MR fails to converge.
|
||||
bool ErrorOnNoConverge; // throw an GRID_ASSERT when the MR fails to converge.
|
||||
// Defaults true.
|
||||
RealD Tolerance;
|
||||
Integer MaxIterations;
|
||||
@@ -59,7 +59,7 @@ template<class Field> class MinimalResidual : public OperatorFunction<Field> {
|
||||
|
||||
// Initial residual computation & set up
|
||||
RealD guess = norm2(psi);
|
||||
assert(std::isnan(guess) == 0);
|
||||
GRID_ASSERT(std::isnan(guess) == 0);
|
||||
|
||||
RealD ssq = norm2(src);
|
||||
RealD rsq = Tolerance * Tolerance * ssq;
|
||||
@@ -136,7 +136,7 @@ template<class Field> class MinimalResidual : public OperatorFunction<Field> {
|
||||
std::cout << GridLogMessage << "MR Time elapsed: Linalg " << LinalgTimer.Elapsed() << std::endl;
|
||||
|
||||
if (ErrorOnNoConverge)
|
||||
assert(true_residual / Tolerance < 10000.0);
|
||||
GRID_ASSERT(true_residual / Tolerance < 10000.0);
|
||||
|
||||
IterationsToComplete = k;
|
||||
|
||||
@@ -148,7 +148,7 @@ template<class Field> class MinimalResidual : public OperatorFunction<Field> {
|
||||
<< std::endl;
|
||||
|
||||
if (ErrorOnNoConverge)
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
|
||||
IterationsToComplete = k;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class MixedPrecisionFlexibleGeneralisedMinimalResidual : public OperatorFunction
|
||||
|
||||
using OperatorFunction<FieldD>::operator();
|
||||
|
||||
bool ErrorOnNoConverge; // Throw an assert when MPFGMRES fails to converge,
|
||||
bool ErrorOnNoConverge; // Throw an GRID_ASSERT when MPFGMRES fails to converge,
|
||||
// defaults to true
|
||||
|
||||
RealD Tolerance;
|
||||
@@ -91,7 +91,7 @@ class MixedPrecisionFlexibleGeneralisedMinimalResidual : public OperatorFunction
|
||||
conformable(psi, src);
|
||||
|
||||
RealD guess = norm2(psi);
|
||||
assert(std::isnan(guess) == 0);
|
||||
GRID_ASSERT(std::isnan(guess) == 0);
|
||||
|
||||
RealD cp;
|
||||
RealD ssq = norm2(src);
|
||||
@@ -150,7 +150,7 @@ class MixedPrecisionFlexibleGeneralisedMinimalResidual : public OperatorFunction
|
||||
std::cout << GridLogMessage << "MPFGMRES did NOT converge" << std::endl;
|
||||
|
||||
if (ErrorOnNoConverge)
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
|
||||
RealD outerLoopBody(LinearOperatorBase<FieldD> &LinOp, const FieldD &src, FieldD &psi, RealD rsq) {
|
||||
@@ -197,7 +197,7 @@ class MixedPrecisionFlexibleGeneralisedMinimalResidual : public OperatorFunction
|
||||
}
|
||||
}
|
||||
|
||||
assert(0); // Never reached
|
||||
GRID_ASSERT(0); // Never reached
|
||||
return cp;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ NAMESPACE_BEGIN(Grid);
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Take a matrix and form an NE solver calling a Herm solver
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
template<class Field> class NormalEquations {
|
||||
template<class Field> class NormalEquations : public LinearFunction<Field>{
|
||||
private:
|
||||
SparseMatrixBase<Field> & _Matrix;
|
||||
OperatorFunction<Field> & _HermitianSolver;
|
||||
@@ -60,7 +60,33 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template<class Field> class HPDSolver {
|
||||
template<class Field> class NormalResidual : public LinearFunction<Field>{
|
||||
private:
|
||||
SparseMatrixBase<Field> & _Matrix;
|
||||
OperatorFunction<Field> & _HermitianSolver;
|
||||
LinearFunction<Field> & _Guess;
|
||||
public:
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// Wrap the usual normal equations trick
|
||||
/////////////////////////////////////////////////////
|
||||
NormalResidual(SparseMatrixBase<Field> &Matrix, OperatorFunction<Field> &HermitianSolver,
|
||||
LinearFunction<Field> &Guess)
|
||||
: _Matrix(Matrix), _HermitianSolver(HermitianSolver), _Guess(Guess) {};
|
||||
|
||||
void operator() (const Field &in, Field &out){
|
||||
|
||||
Field res(in.Grid());
|
||||
Field tmp(in.Grid());
|
||||
|
||||
MMdagLinearOperator<SparseMatrixBase<Field>,Field> MMdagOp(_Matrix);
|
||||
_Guess(in,res);
|
||||
_HermitianSolver(MMdagOp,in,res); // M Mdag res = in ;
|
||||
_Matrix.Mdag(res,out); // out = Mdag res
|
||||
}
|
||||
};
|
||||
|
||||
template<class Field> class HPDSolver : public LinearFunction<Field> {
|
||||
private:
|
||||
LinearOperatorBase<Field> & _Matrix;
|
||||
OperatorFunction<Field> & _HermitianSolver;
|
||||
@@ -78,13 +104,13 @@ public:
|
||||
void operator() (const Field &in, Field &out){
|
||||
|
||||
_Guess(in,out);
|
||||
_HermitianSolver(_Matrix,in,out); // Mdag M out = Mdag in
|
||||
_HermitianSolver(_Matrix,in,out); //M out = in
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<class Field> class MdagMSolver {
|
||||
template<class Field> class MdagMSolver : public LinearFunction<Field> {
|
||||
private:
|
||||
SparseMatrixBase<Field> & _Matrix;
|
||||
OperatorFunction<Field> & _HermitianSolver;
|
||||
|
||||
@@ -20,7 +20,7 @@ template<class Field> class PowerMethod
|
||||
RealD evalMaxApprox = 0.0;
|
||||
auto src_n = src;
|
||||
auto tmp = src;
|
||||
const int _MAX_ITER_EST_ = 50;
|
||||
const int _MAX_ITER_EST_ = 200;
|
||||
|
||||
for (int i=0;i<_MAX_ITER_EST_;i++) {
|
||||
|
||||
@@ -30,18 +30,82 @@ template<class Field> class PowerMethod
|
||||
RealD vden = norm2(src_n);
|
||||
RealD na = vnum/vden;
|
||||
|
||||
std::cout << GridLogIterative << "PowerMethod: Current approximation of largest eigenvalue " << na << std::endl;
|
||||
std::cout << GridLogMessage << "PowerMethod: Current approximation of largest eigenvalue " << na << std::endl;
|
||||
|
||||
if ( (fabs(evalMaxApprox/na - 1.0) < 0.001) || (i==_MAX_ITER_EST_-1) ) {
|
||||
evalMaxApprox = na;
|
||||
std::cout << GridLogMessage << " Approximation of largest eigenvalue: " << evalMaxApprox << std::endl;
|
||||
return evalMaxApprox;
|
||||
}
|
||||
// if ( (fabs(evalMaxApprox/na - 1.0) < 0.0001) || (i==_MAX_ITER_EST_-1) ) {
|
||||
// evalMaxApprox = na;
|
||||
// return evalMaxApprox;
|
||||
// }
|
||||
evalMaxApprox = na;
|
||||
src_n = tmp;
|
||||
}
|
||||
assert(0);
|
||||
return 0;
|
||||
std::cout << GridLogMessage << " Approximation of largest eigenvalue: " << evalMaxApprox << std::endl;
|
||||
return evalMaxApprox;
|
||||
}
|
||||
};
|
||||
|
||||
// Non-Hermitian sibling of PowerMethod. Drives Op() -- the operator the
|
||||
// caller actually applies -- rather than HermOp(), so it returns |lambda_max|
|
||||
// of a NON-Hermitian operator and reports the spectral-edge diagnostics the
|
||||
// Hermitian PowerMethod cannot:
|
||||
// step 0 : |A v|/|v| on the (random) start src -- a one-sample lower bound
|
||||
// on sigma_max(A).
|
||||
// step k : |A v_k| -> |lambda_max| as v_k -> the dominant eigenvector; the
|
||||
// complex Rayleigh quotient <v,Av> gives its phase (real => on the
|
||||
// axis, a non-converging oscillation => a conjugate pair of equal
|
||||
// modulus at the top).
|
||||
// return : |lambda_max|, with a SUMMARY line flagging non-normality when the
|
||||
// step-0 sigma_max lower bound sits well above the converged
|
||||
// |lambda_max| -- the case where the numerical range extends beyond
|
||||
// the spectrum and a spectrum-based smoother/Chebyshev bound is
|
||||
// unsafe (the field of values must be used instead).
|
||||
template<class Field> class PowerMethodNonHermitian
|
||||
{
|
||||
public:
|
||||
|
||||
template<typename T> static RealD normalise(T& v)
|
||||
{
|
||||
RealD nn = sqrt(norm2(v));
|
||||
v = v * (1.0/nn);
|
||||
return nn;
|
||||
}
|
||||
|
||||
RealD operator()(LinearOperatorBase<Field> &Op, const Field &src)
|
||||
{
|
||||
GridBase *grid = src.Grid();
|
||||
Field v(grid), Av(grid);
|
||||
v = src;
|
||||
|
||||
RealD ratio = 0.0, ratio0 = 0.0;
|
||||
ComplexD rq(0.0);
|
||||
const int _MAX_ITER_EST_ = 200;
|
||||
|
||||
for (int i=0;i<_MAX_ITER_EST_;i++) {
|
||||
normalise(v); // v is now unit
|
||||
Op.Op(v,Av);
|
||||
ratio = sqrt(norm2(Av)); // |A v| = |lambda_max| in the limit
|
||||
rq = innerProduct(v,Av); // complex Rayleigh quotient
|
||||
|
||||
if ( i==0 ) {
|
||||
ratio0 = ratio;
|
||||
std::cout << GridLogMessage << "PowerMethodNonHermitian: step 0 (random v): |Av|/|v| = "
|
||||
<< ratio << " [lower bound on sigma_max]" << std::endl;
|
||||
}
|
||||
if ( (i%10==0) || (i==_MAX_ITER_EST_-1) )
|
||||
std::cout << GridLogMessage << "PowerMethodNonHermitian: step " << i << " |Av|/|v| = " << ratio
|
||||
<< " Rayleigh (" << real(rq) << "," << imag(rq) << ")" << std::endl;
|
||||
|
||||
v = Av;
|
||||
}
|
||||
|
||||
std::cout << GridLogMessage << "PowerMethodNonHermitian: |lambda_max| ~ " << ratio
|
||||
<< " Rayleigh (" << real(rq) << "," << imag(rq) << ")"
|
||||
<< " phase " << atan2(imag(rq),real(rq)) << " rad"
|
||||
<< " step-0/converged = " << ratio0/ratio
|
||||
<< (ratio0/ratio > 1.2 ? " ** non-normal: sigma_max well above |lambda_max| **"
|
||||
: " (near-normal)")
|
||||
<< std::endl;
|
||||
return ratio;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
namespace Grid {
|
||||
|
||||
class Band
|
||||
{
|
||||
RealD lo, hi;
|
||||
public:
|
||||
Band(RealD _lo,RealD _hi)
|
||||
{
|
||||
lo=_lo;
|
||||
hi=_hi;
|
||||
}
|
||||
RealD operator() (RealD x){
|
||||
if ( x>lo && x<hi ){
|
||||
return 1.0;
|
||||
} else {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class PowerSpectrum
|
||||
{
|
||||
public:
|
||||
|
||||
template<typename T> static RealD normalise(T& v)
|
||||
{
|
||||
RealD nn = norm2(v);
|
||||
nn = sqrt(nn);
|
||||
v = v * (1.0/nn);
|
||||
return nn;
|
||||
}
|
||||
|
||||
std::vector<RealD> ranges;
|
||||
std::vector<int> order;
|
||||
|
||||
PowerSpectrum( std::vector<RealD> &bins, std::vector<int> &_order ) : ranges(bins), order(_order) { };
|
||||
|
||||
template<class Field>
|
||||
RealD operator()(LinearOperatorBase<Field> &HermOp, const Field &src)
|
||||
{
|
||||
GridBase *grid = src.Grid();
|
||||
int N=ranges.size();
|
||||
RealD hi = ranges[N-1];
|
||||
|
||||
RealD lo_band = 0.0;
|
||||
RealD hi_band;
|
||||
RealD nn=norm2(src);
|
||||
RealD ss=0.0;
|
||||
|
||||
Field tmp = src;
|
||||
|
||||
for(int b=0;b<N;b++){
|
||||
hi_band = ranges[b];
|
||||
Band Notch(lo_band,hi_band);
|
||||
|
||||
Chebyshev<Field> polynomial;
|
||||
polynomial.Init(0.0,hi,order[b],Notch);
|
||||
polynomial.JacksonSmooth();
|
||||
|
||||
polynomial(HermOp,src,tmp) ;
|
||||
|
||||
RealD p=norm2(tmp);
|
||||
ss=ss+p;
|
||||
std::cout << GridLogMessage << " PowerSpectrum Band["<<lo_band<<","<<hi_band<<"] power "<<norm2(tmp)/nn<<std::endl;
|
||||
|
||||
lo_band=hi_band;
|
||||
}
|
||||
std::cout << GridLogMessage << " PowerSpectrum total power "<<ss/nn<<std::endl;
|
||||
std::cout << GridLogMessage << " PowerSpectrum total power (unnormalised) "<<nn<<std::endl;
|
||||
|
||||
return 0;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -112,7 +112,7 @@ public:
|
||||
}
|
||||
|
||||
std::cout<<GridLogMessage<<"PrecConjugateResidual did NOT converge"<<std::endl;
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
};
|
||||
NAMESPACE_END(Grid);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./lib/algorithms/iterative/PrecGeneralisedConjugateResidual.h
|
||||
|
||||
@@ -28,212 +28,48 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
|
||||
/* END LEGAL */
|
||||
#ifndef GRID_PREC_GCR_H
|
||||
#define GRID_PREC_GCR_H
|
||||
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//VPGCR Abe and Zhang, 2005.
|
||||
//INTERNATIONAL JOURNAL OF NUMERICAL ANALYSIS AND MODELING
|
||||
//Computing and Information Volume 2, Number 2, Pages 147-161
|
||||
//NB. Likely not original reference since they are focussing on a preconditioner variant.
|
||||
// but VPGCR was nicely written up in their paper
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
#define GCRLogLevel std::cout << GridLogMessage <<std::string(level,'\t')<< " Level "<<level<<" "
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Hermitian spelling of PrecGeneralisedConjugateResidualNonHermitian.
|
||||
//
|
||||
// The algorithm is the same; only the operator entry point differs. The solver
|
||||
// drives Linop.Op(), while a caller of this class means "invert HermOp", so the
|
||||
// operator is wrapped in HermOpAdaptor. The wrap is what makes the two classes
|
||||
// one: for an operator whose Op() already IS its HermOp() (HermitianLinearOperator,
|
||||
// Gamma5R5HermitianLinearOperator, HermOpAdaptor) it changes nothing, and for one
|
||||
// where they differ (MdagMLinearOperator: Op()=M, HermOp()=MdagM) it preserves the
|
||||
// Hermitian meaning the caller asked for.
|
||||
//
|
||||
// A non-zero initial guess is respected. Callers that guarantee x0 = 0 can call
|
||||
// SetZeroGuess(1) to skip the first r0 = src - A x0 apply.
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
template<class Field>
|
||||
class PrecGeneralisedConjugateResidual : public LinearFunction<Field> {
|
||||
public:
|
||||
// Declaration order is initialisation order: the adaptor is complete before
|
||||
// the solver binds its reference to it.
|
||||
HermOpAdaptor<Field> HermLinop;
|
||||
PrecGeneralisedConjugateResidualNonHermitian<Field> GCR;
|
||||
public:
|
||||
using LinearFunction<Field>::operator();
|
||||
RealD Tolerance;
|
||||
Integer MaxIterations;
|
||||
int verbose;
|
||||
int mmax;
|
||||
int nstep;
|
||||
int steps;
|
||||
int level;
|
||||
GridStopWatch PrecTimer;
|
||||
GridStopWatch MatTimer;
|
||||
GridStopWatch LinalgTimer;
|
||||
|
||||
LinearFunction<Field> &Preconditioner;
|
||||
LinearOperatorBase<Field> &Linop;
|
||||
PrecGeneralisedConjugateResidual(RealD tol,Integer maxit,LinearOperatorBase<Field> &Linop,
|
||||
LinearFunction<Field> &Prec,int mmax,int nstep)
|
||||
: HermLinop(Linop),
|
||||
GCR(tol,maxit,HermLinop,Prec,mmax,nstep) {};
|
||||
|
||||
void Level(int lv) { level=lv; };
|
||||
void operator() (const Field &src, Field &psi) { GCR(src,psi); };
|
||||
|
||||
PrecGeneralisedConjugateResidual(RealD tol,Integer maxit,LinearOperatorBase<Field> &_Linop,LinearFunction<Field> &Prec,int _mmax,int _nstep) :
|
||||
Tolerance(tol),
|
||||
MaxIterations(maxit),
|
||||
Linop(_Linop),
|
||||
Preconditioner(Prec),
|
||||
mmax(_mmax),
|
||||
nstep(_nstep)
|
||||
{
|
||||
level=1;
|
||||
verbose=1;
|
||||
};
|
||||
|
||||
void operator() (const Field &src, Field &psi){
|
||||
|
||||
psi=Zero();
|
||||
RealD cp, ssq,rsq;
|
||||
ssq=norm2(src);
|
||||
rsq=Tolerance*Tolerance*ssq;
|
||||
|
||||
Field r(src.Grid());
|
||||
|
||||
PrecTimer.Reset();
|
||||
MatTimer.Reset();
|
||||
LinalgTimer.Reset();
|
||||
|
||||
GridStopWatch SolverTimer;
|
||||
SolverTimer.Start();
|
||||
|
||||
steps=0;
|
||||
for(int k=0;k<MaxIterations;k++){
|
||||
|
||||
cp=GCRnStep(src,psi,rsq);
|
||||
|
||||
GCRLogLevel <<"PGCR("<<mmax<<","<<nstep<<") "<< steps <<" steps cp = "<<cp<<" target "<<rsq <<std::endl;
|
||||
|
||||
if(cp<rsq) {
|
||||
|
||||
SolverTimer.Stop();
|
||||
|
||||
Linop.HermOp(psi,r);
|
||||
axpy(r,-1.0,src,r);
|
||||
RealD tr = norm2(r);
|
||||
GCRLogLevel<<"PGCR: Converged on iteration " <<steps
|
||||
<< " computed residual "<<sqrt(cp/ssq)
|
||||
<< " true residual " <<sqrt(tr/ssq)
|
||||
<< " target " <<Tolerance <<std::endl;
|
||||
|
||||
GCRLogLevel<<"PGCR Time elapsed: Total "<< SolverTimer.Elapsed() <<std::endl;
|
||||
/*
|
||||
GCRLogLevel<<"PGCR Time elapsed: Precon "<< PrecTimer.Elapsed() <<std::endl;
|
||||
GCRLogLevel<<"PGCR Time elapsed: Matrix "<< MatTimer.Elapsed() <<std::endl;
|
||||
GCRLogLevel<<"PGCR Time elapsed: Linalg "<< LinalgTimer.Elapsed() <<std::endl;
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
GCRLogLevel<<"Variable Preconditioned GCR did not converge"<<std::endl;
|
||||
// assert(0);
|
||||
}
|
||||
|
||||
RealD GCRnStep(const Field &src, Field &psi,RealD rsq){
|
||||
|
||||
RealD cp;
|
||||
RealD a, b;
|
||||
RealD zAz, zAAz;
|
||||
RealD rq;
|
||||
|
||||
GridBase *grid = src.Grid();
|
||||
|
||||
Field r(grid);
|
||||
Field z(grid);
|
||||
Field tmp(grid);
|
||||
Field ttmp(grid);
|
||||
Field Az(grid);
|
||||
|
||||
////////////////////////////////
|
||||
// history for flexible orthog
|
||||
////////////////////////////////
|
||||
std::vector<Field> q(mmax,grid);
|
||||
std::vector<Field> p(mmax,grid);
|
||||
std::vector<RealD> qq(mmax);
|
||||
|
||||
GCRLogLevel<< "PGCR nStep("<<nstep<<")"<<std::endl;
|
||||
|
||||
//////////////////////////////////
|
||||
// initial guess x0 is taken as nonzero.
|
||||
// r0=src-A x0 = src
|
||||
//////////////////////////////////
|
||||
MatTimer.Start();
|
||||
Linop.HermOpAndNorm(psi,Az,zAz,zAAz);
|
||||
MatTimer.Stop();
|
||||
|
||||
|
||||
LinalgTimer.Start();
|
||||
r=src-Az;
|
||||
LinalgTimer.Stop();
|
||||
GCRLogLevel<< "PGCR true residual r = src - A psi "<<norm2(r) <<std::endl;
|
||||
|
||||
/////////////////////
|
||||
// p = Prec(r)
|
||||
/////////////////////
|
||||
|
||||
PrecTimer.Start();
|
||||
Preconditioner(r,z);
|
||||
PrecTimer.Stop();
|
||||
|
||||
MatTimer.Start();
|
||||
Linop.HermOpAndNorm(z,Az,zAz,zAAz);
|
||||
MatTimer.Stop();
|
||||
|
||||
LinalgTimer.Start();
|
||||
|
||||
//p[0],q[0],qq[0]
|
||||
p[0]= z;
|
||||
q[0]= Az;
|
||||
qq[0]= zAAz;
|
||||
|
||||
cp =norm2(r);
|
||||
LinalgTimer.Stop();
|
||||
|
||||
for(int k=0;k<nstep;k++){
|
||||
|
||||
steps++;
|
||||
|
||||
int kp = k+1;
|
||||
int peri_k = k %mmax;
|
||||
int peri_kp= kp%mmax;
|
||||
|
||||
LinalgTimer.Start();
|
||||
rq= real(innerProduct(r,q[peri_k])); // what if rAr not real?
|
||||
a = rq/qq[peri_k];
|
||||
|
||||
axpy(psi,a,p[peri_k],psi);
|
||||
|
||||
cp = axpy_norm(r,-a,q[peri_k],r);
|
||||
LinalgTimer.Stop();
|
||||
|
||||
GCRLogLevel<< "PGCR step["<<steps<<"] resid " << cp << " target " <<rsq<<std::endl;
|
||||
|
||||
if((k==nstep-1)||(cp<rsq)){
|
||||
return cp;
|
||||
}
|
||||
|
||||
|
||||
PrecTimer.Start();
|
||||
Preconditioner(r,z);// solve Az = r
|
||||
PrecTimer.Stop();
|
||||
|
||||
MatTimer.Start();
|
||||
Linop.HermOpAndNorm(z,Az,zAz,zAAz);
|
||||
MatTimer.Stop();
|
||||
|
||||
LinalgTimer.Start();
|
||||
|
||||
q[peri_kp]=Az;
|
||||
p[peri_kp]=z;
|
||||
|
||||
int northog = ((kp)>(mmax-1))?(mmax-1):(kp); // if more than mmax done, we orthog all mmax history.
|
||||
for(int back=0;back<northog;back++){
|
||||
|
||||
int peri_back=(k-back)%mmax; assert((k-back)>=0);
|
||||
|
||||
b=-real(innerProduct(q[peri_back],Az))/qq[peri_back];
|
||||
p[peri_kp]=p[peri_kp]+b*p[peri_back];
|
||||
q[peri_kp]=q[peri_kp]+b*q[peri_back];
|
||||
|
||||
}
|
||||
qq[peri_kp]=norm2(q[peri_kp]); // could use axpy_norm
|
||||
LinalgTimer.Stop();
|
||||
}
|
||||
assert(0); // never reached
|
||||
return cp;
|
||||
}
|
||||
void Name(std::string n) { GCR.Name(n); };
|
||||
void Level(int n) { GCR.Level(n); };
|
||||
void SetZeroGuess(int z) { GCR.SetZeroGuess(z); };
|
||||
int Steps(void) const { return GCR.Steps(); };
|
||||
void LogCoefficients(int l) { GCR.LogCoefficients(l); };
|
||||
void SetCoefficientRecorder(GCRCoefficients *r){ GCR.SetCoefficientRecorder(r); };
|
||||
void ReleaseHistory(void) { GCR.ReleaseHistory(); };
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
#endif
|
||||
@@ -28,6 +28,7 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
|
||||
/* END LEGAL */
|
||||
#ifndef GRID_PREC_GCR_NON_HERM_H
|
||||
#define GRID_PREC_GCR_NON_HERM_H
|
||||
#include <Grid/algorithms/iterative/GCRCoefficients.h>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//VPGCR Abe and Zhang, 2005.
|
||||
@@ -38,13 +39,57 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
#define GCRLogLevel std::cout << GridLogMessage <<std::string(level,'\t')<< " Level "<<level<<" "
|
||||
#define GCRLogLevel std::cout << GridLogMessage <<std::string(level,'\t')<< name<<" "
|
||||
|
||||
template<class Field>
|
||||
class PrecGeneralisedConjugateResidualNonHermitian : public LinearFunction<Field> {
|
||||
public:
|
||||
public:
|
||||
using LinearFunction<Field>::operator();
|
||||
|
||||
PrecGeneralisedConjugateResidualNonHermitian(RealD tol,Integer maxit,LinearOperatorBase<Field> &_Linop,LinearFunction<Field> &Prec,int _mmax,int _nstep) :
|
||||
Tolerance(tol),
|
||||
MaxIterations(maxit),
|
||||
mmax(_mmax),
|
||||
nstep(_nstep),
|
||||
Preconditioner(Prec),
|
||||
Linop(_Linop)
|
||||
{
|
||||
Level(1);
|
||||
verbose=1;
|
||||
trivial_prec = Preconditioner.isTrivial();
|
||||
};
|
||||
|
||||
// The name is also the trace range's, so a profile separates the four
|
||||
// instances (Fouter, Fsmoother, Couter, Csmoother) that otherwise nest
|
||||
// indistinguishably as one shared range.
|
||||
void Name(std::string _name) { name = _name; trace_step = name + " PGCR_step"; };
|
||||
|
||||
void Level(int n) { Name("Level " + std::to_string(n)); level = n; }
|
||||
|
||||
void SetZeroGuess(int z) { ZeroGuess = z; };
|
||||
|
||||
// Steps taken by the last solve.
|
||||
int Steps(void) const { return steps; };
|
||||
|
||||
// Coefficient logging: one line per step with the step length a_k and the
|
||||
// orthogonalisation coefficients b_{k,j}. These are the data from which a
|
||||
// FIXED polynomial smoother can be harvested: if they are stable from call
|
||||
// to call, the adaptive GCR can be replaced by a stationary p(A) with the
|
||||
// same applies and no reductions. Off by default; boss rank prints.
|
||||
void LogCoefficients(int l) { LogCoeffs = l; };
|
||||
|
||||
// Optional recorder of the per-step coefficients (means over calls), for
|
||||
// replay by GCRReplaySmoother (Smoothers.h). Records only; no effect on
|
||||
// the iteration.
|
||||
void SetCoefficientRecorder(GCRCoefficients *r) { Recorder = r; if(r) r->mmax = mmax; };
|
||||
|
||||
// Free the persistent history (e.g. when this solver is replaced by a
|
||||
// replayed polynomial): re-made on the next call if ever needed again.
|
||||
void ReleaseHistory(void) { q.clear(); p.clear(); qq.clear(); hist_grid = nullptr; };
|
||||
|
||||
private:
|
||||
RealD Tolerance;
|
||||
RealD SSQ;
|
||||
Integer MaxIterations;
|
||||
int verbose;
|
||||
int mmax;
|
||||
@@ -54,29 +99,30 @@ public:
|
||||
GridStopWatch PrecTimer;
|
||||
GridStopWatch MatTimer;
|
||||
GridStopWatch LinalgTimer;
|
||||
std::string name;
|
||||
std::string trace_step = "PGCR_step"; // name + step; see Name()
|
||||
int ZeroGuess = 0; // caller contract: guess is always zero => first-cycle r0 = src, skip the apply
|
||||
// persistent GCR history (see GCRnStep)
|
||||
GridBase *hist_grid = nullptr;
|
||||
std::vector<Field> q;
|
||||
std::vector<Field> p;
|
||||
std::vector<RealD> qq;
|
||||
int FirstCycle = 0;
|
||||
int LogCoeffs = 0;
|
||||
int trivial_prec; // Preconditioner is the identity: p == r, copies fold away
|
||||
GCRCoefficients *Recorder = nullptr;
|
||||
|
||||
LinearFunction<Field> &Preconditioner;
|
||||
LinearOperatorBase<Field> &Linop;
|
||||
|
||||
void Level(int lv) { level=lv; };
|
||||
|
||||
PrecGeneralisedConjugateResidualNonHermitian(RealD tol,Integer maxit,LinearOperatorBase<Field> &_Linop,LinearFunction<Field> &Prec,int _mmax,int _nstep) :
|
||||
Tolerance(tol),
|
||||
MaxIterations(maxit),
|
||||
Linop(_Linop),
|
||||
Preconditioner(Prec),
|
||||
mmax(_mmax),
|
||||
nstep(_nstep)
|
||||
{
|
||||
level=1;
|
||||
verbose=1;
|
||||
};
|
||||
public:
|
||||
|
||||
void operator() (const Field &src, Field &psi){
|
||||
|
||||
psi=Zero();
|
||||
// psi=Zero();
|
||||
RealD cp, ssq,rsq;
|
||||
ssq=norm2(src);
|
||||
SSQ=ssq;
|
||||
rsq=Tolerance*Tolerance*ssq;
|
||||
|
||||
Field r(src.Grid());
|
||||
@@ -89,11 +135,12 @@ public:
|
||||
SolverTimer.Start();
|
||||
|
||||
steps=0;
|
||||
FirstCycle=1;
|
||||
for(int k=0;k<MaxIterations;k++){
|
||||
|
||||
cp=GCRnStep(src,psi,rsq);
|
||||
|
||||
GCRLogLevel <<"PGCR("<<mmax<<","<<nstep<<") "<< steps <<" steps cp = "<<cp<<" target "<<rsq <<std::endl;
|
||||
GCRLogLevel <<"PGCR("<<mmax<<","<<nstep<<") "<< steps <<" steps cp = "<<sqrt(cp/ssq)<<" target "<<sqrt(rsq/ssq) <<std::endl;
|
||||
|
||||
if(cp<rsq) {
|
||||
|
||||
@@ -113,77 +160,90 @@ public:
|
||||
|
||||
}
|
||||
GCRLogLevel<<"Variable Preconditioned GCR did not converge"<<std::endl;
|
||||
// assert(0);
|
||||
// GRID_ASSERT(0);
|
||||
}
|
||||
|
||||
private:
|
||||
RealD GCRnStep(const Field &src, Field &psi,RealD rsq){
|
||||
|
||||
RealD cp;
|
||||
ComplexD a, b;
|
||||
// ComplexD zAz;
|
||||
RealD zAAz;
|
||||
ComplexD rq;
|
||||
|
||||
GridBase *grid = src.Grid();
|
||||
|
||||
// Only r and one scratch for the restart residual; the new p/q directions
|
||||
// are produced directly in their persistent history slots.
|
||||
Field r(grid);
|
||||
Field z(grid);
|
||||
Field tmp(grid);
|
||||
Field ttmp(grid);
|
||||
Field Az(grid);
|
||||
|
||||
////////////////////////////////
|
||||
// history for flexible orthog
|
||||
////////////////////////////////
|
||||
std::vector<Field> q(mmax,grid);
|
||||
std::vector<Field> p(mmax,grid);
|
||||
std::vector<RealD> qq(mmax);
|
||||
// History arrays are PERSISTENT across calls (allocated once per grid,
|
||||
// re-made only if the grid or mmax changes). The per-call form
|
||||
// std::vector<Field>(mmax,grid) built a temporary and copy-constructed it
|
||||
// mmax times on every restart cycle -- measured ~5 ms per fine-smoother
|
||||
// call. Safe: every entry is written before it is read within a cycle
|
||||
// (q[kp],p[kp] assigned before the northog loop can reach them), so no
|
||||
// stale content is ever consumed.
|
||||
if ( hist_grid != grid || (int)q.size() != mmax ) {
|
||||
q.clear(); p.clear();
|
||||
q.reserve(mmax); p.reserve(mmax);
|
||||
for(int i=0;i<mmax;i++){ q.emplace_back(grid); p.emplace_back(grid); }
|
||||
qq.assign(mmax, 0.0);
|
||||
hist_grid = grid;
|
||||
}
|
||||
|
||||
GCRLogLevel<< "PGCR nStep("<<nstep<<")"<<std::endl;
|
||||
|
||||
//////////////////////////////////
|
||||
// initial guess x0 is taken as nonzero.
|
||||
// r0=src-A x0 = src
|
||||
// r0 = src - A x0. ZeroGuess: on the first cycle x0==0 by caller
|
||||
// contract (enforced here), so r0 = src exactly; skip the apply.
|
||||
// Restart cycles (psi!=0) always do the full computation.
|
||||
//////////////////////////////////
|
||||
MatTimer.Start();
|
||||
Linop.Op(psi,Az);
|
||||
// zAz = innerProduct(Az,psi);
|
||||
zAAz= norm2(Az);
|
||||
MatTimer.Stop();
|
||||
|
||||
// The residual is READ through rp and only materialised in r when it is
|
||||
// first written, at the k=0 update. On a zero-guess first cycle r0 IS src,
|
||||
// so no copy is made; a restart cycle computes r and rp points at it.
|
||||
const Field *rp = &src;
|
||||
GRID_ASSERT(nstep>=1); // psi is first written at k=0; see zero_start below
|
||||
int zero_start = (ZeroGuess && FirstCycle);
|
||||
if ( !zero_start ) {
|
||||
MatTimer.Start();
|
||||
Linop.Op(psi,Az);
|
||||
MatTimer.Stop();
|
||||
LinalgTimer.Start();
|
||||
r=src-Az;
|
||||
LinalgTimer.Stop();
|
||||
rp = &r;
|
||||
}
|
||||
FirstCycle=0;
|
||||
|
||||
LinalgTimer.Start();
|
||||
r=src-Az;
|
||||
LinalgTimer.Stop();
|
||||
GCRLogLevel<< "PGCR true residual r = src - A psi "<<norm2(r) <<std::endl;
|
||||
|
||||
/////////////////////
|
||||
// p = Prec(r)
|
||||
/////////////////////
|
||||
|
||||
// p[0] = Prec(r), q[0] = A p[0], written straight into the history slots.
|
||||
// This copy is forced even for a trivial preconditioner: p[0] is history and
|
||||
// must survive while the residual evolves.
|
||||
PrecTimer.Start();
|
||||
Preconditioner(r,z);
|
||||
Preconditioner(*rp,p[0]);
|
||||
PrecTimer.Stop();
|
||||
|
||||
MatTimer.Start();
|
||||
Linop.Op(z,Az);
|
||||
Linop.Op(p[0],q[0]);
|
||||
MatTimer.Stop();
|
||||
|
||||
LinalgTimer.Start();
|
||||
|
||||
// zAz = innerProduct(Az,psi);
|
||||
zAAz= norm2(Az);
|
||||
qq[0]= norm2(q[0]);
|
||||
|
||||
//p[0],q[0],qq[0]
|
||||
p[0]= z;
|
||||
q[0]= Az;
|
||||
qq[0]= zAAz;
|
||||
|
||||
cp =norm2(r);
|
||||
cp =norm2(*rp);
|
||||
LinalgTimer.Stop();
|
||||
GCRLogLevel<< "PGCR true residual "<< sqrt(cp/SSQ) <<std::endl;
|
||||
|
||||
for(int k=0;k<nstep;k++){
|
||||
|
||||
GRID_TRACE(trace_step.c_str());
|
||||
steps++;
|
||||
|
||||
int kp = k+1;
|
||||
@@ -191,52 +251,108 @@ public:
|
||||
int peri_kp= kp%mmax;
|
||||
|
||||
LinalgTimer.Start();
|
||||
rq= innerProduct(q[peri_k],r); // what if rAr not real?
|
||||
rq= innerProduct(q[peri_k],*rp); // what if rAr not real?
|
||||
a = rq/qq[peri_k];
|
||||
if ( Recorder ) Recorder->RecordA(k,a);
|
||||
|
||||
axpy(psi,a,p[peri_k],psi);
|
||||
// On a zero-guess first step psi is still unwritten, and the update would
|
||||
// be a*p added to zero: assign it instead, so psi is never zeroed.
|
||||
if ( zero_start && (k==0) ) psi = a*p[peri_k];
|
||||
else axpy(psi,a,p[peri_k],psi);
|
||||
|
||||
cp = axpy_norm(r,-a,q[peri_k],r);
|
||||
#ifdef GRID_GCR_RESIDUAL_RECURRENCE
|
||||
// |r - a q|^2 = |r|^2 - |<q,r>|^2/|q|^2 with a = <q,r>/|q|^2, so the new
|
||||
// residual norm follows from scalars already in hand and the reduction in
|
||||
// axpy_norm is not needed. cp then DRIFTS rather than being measured; the
|
||||
// true residual is recomputed at restart and at convergence, which bounds
|
||||
// the exposure. The drift only matters for an instance driven to a tight
|
||||
// tolerance -- the outer solver -- and there it is the same computed-vs-true
|
||||
// residual gap that CG lives with; the smoother and coarse instances run at
|
||||
// fixed work or loose tolerance and are indifferent to it.
|
||||
// Explicit re/im: ComplexD is thrust::complex under HIP.
|
||||
axpy(r,-a,q[peri_k],*rp);
|
||||
cp = cp - (real(rq)*real(rq)+imag(rq)*imag(rq))/qq[peri_k];
|
||||
if ( cp < 0.0 ) cp = 0.0; // drift can undershoot; keeps the log finite
|
||||
#else
|
||||
cp = axpy_norm(r,-a,q[peri_k],*rp);
|
||||
#endif
|
||||
rp = &r; // the residual now lives in r; src is untouched from here on
|
||||
LinalgTimer.Stop();
|
||||
if ( LogCoeffs ) {
|
||||
GCRLogLevel<<"coeff["<<k<<"] a=("<<real(a)<<","<<imag(a)<<")"
|
||||
<<" |r|/|r0|="<<sqrt(cp/SSQ)<<std::endl;
|
||||
}
|
||||
|
||||
GCRLogLevel<< "PGCR step["<<steps<<"] resid " << cp << " target " <<rsq<<std::endl;
|
||||
GCRLogLevel<< "PGCR step["<<steps<<"] resid " << sqrt(cp/SSQ)<<std::endl;
|
||||
|
||||
if((k==nstep-1)||(cp<rsq)){
|
||||
return cp;
|
||||
}
|
||||
|
||||
|
||||
PrecTimer.Start();
|
||||
Preconditioner(r,z);// solve Az = r
|
||||
PrecTimer.Stop();
|
||||
|
||||
MatTimer.Start();
|
||||
Linop.Op(z,Az);
|
||||
MatTimer.Stop();
|
||||
// zAz = innerProduct(Az,psi);
|
||||
zAAz= norm2(Az);
|
||||
// New direction written straight into its history slot: p = Prec(r), q = A p.
|
||||
// For a trivial preconditioner p[kp] would only be a copy of the residual,
|
||||
// so the operator is applied to the residual directly and the copy folds
|
||||
// into the orthogonalisation pass below.
|
||||
if ( trivial_prec ) {
|
||||
MatTimer.Start();
|
||||
Linop.Op(*rp,q[peri_kp]);
|
||||
MatTimer.Stop();
|
||||
} else {
|
||||
PrecTimer.Start();
|
||||
Preconditioner(*rp,p[peri_kp]);
|
||||
PrecTimer.Stop();
|
||||
MatTimer.Start();
|
||||
Linop.Op(p[peri_kp],q[peri_kp]);
|
||||
MatTimer.Stop();
|
||||
}
|
||||
|
||||
LinalgTimer.Start();
|
||||
|
||||
q[peri_kp]=Az;
|
||||
p[peri_kp]=z;
|
||||
|
||||
int northog = ((kp)>(mmax-1))?(mmax-1):(kp); // if more than mmax done, we orthog all mmax history.
|
||||
std::ostringstream bs;
|
||||
|
||||
// Classical Gram-Schmidt: every coefficient is taken against the
|
||||
// UN-updated new q (so all northog inner products are independent and
|
||||
// batchable), then the window is applied. The coefficient is complex:
|
||||
// for a non-Hermitian operator <q_j,Aq> is complex and keeping only the
|
||||
// real part left the q's non-orthogonal.
|
||||
// Batched: one fused kernel + one reduction for all coefficients,
|
||||
// one fused pass per update (independent of mmax).
|
||||
std::vector<const Field*> qwin(northog), pwin(northog);
|
||||
for(int back=0;back<northog;back++){
|
||||
|
||||
int peri_back=(k-back)%mmax; assert((k-back)>=0);
|
||||
|
||||
b=-real(innerProduct(q[peri_back],Az))/qq[peri_back];
|
||||
p[peri_kp]=p[peri_kp]+b*p[peri_back];
|
||||
q[peri_kp]=q[peri_kp]+b*q[peri_back];
|
||||
|
||||
int peri_back=(k-back)%mmax; GRID_ASSERT((k-back)>=0);
|
||||
GRID_ASSERT(peri_back!=peri_kp);
|
||||
qwin[back] = &q[peri_back];
|
||||
pwin[back] = &p[peri_back];
|
||||
}
|
||||
std::vector<ComplexD> bcoef;
|
||||
innerProductMulti(bcoef,qwin,q[peri_kp]);
|
||||
for(int back=0;back<northog;back++){
|
||||
int peri_back=(k-back)%mmax;
|
||||
bcoef[back] = -bcoef[back]/qq[peri_back];
|
||||
if ( LogCoeffs ) bs<<" b["<<back<<"]="<<bcoef[back];
|
||||
}
|
||||
if ( Recorder && northog ) Recorder->RecordB(k,bcoef);
|
||||
// axpyMultiNorm returns |q|^2 from the update pass, so qq is complete here:
|
||||
// a separate norm2 would be a second reduction and a second GlobalSum.
|
||||
if ( northog ) {
|
||||
if ( trivial_prec ) axpyMultiFrom(p[peri_kp],*rp,bcoef,pwin);
|
||||
else axpyMulti(p[peri_kp],bcoef,pwin);
|
||||
qq[peri_kp]=axpyMultiNorm(q[peri_kp],bcoef,qwin);
|
||||
} else {
|
||||
if ( trivial_prec ) p[peri_kp] = *rp; // no pass to fold the copy into
|
||||
qq[peri_kp]=norm2(q[peri_kp]);
|
||||
}
|
||||
if ( LogCoeffs && northog ) {
|
||||
GCRLogLevel<<"coeff["<<k<<"]"<<bs.str()<<std::endl;
|
||||
}
|
||||
qq[peri_kp]=norm2(q[peri_kp]); // could use axpy_norm
|
||||
LinalgTimer.Stop();
|
||||
}
|
||||
assert(0); // never reached
|
||||
GRID_ASSERT(0); // never reached
|
||||
return cp;
|
||||
}
|
||||
};
|
||||
NAMESPACE_END(Grid);
|
||||
|
||||
#undef GCRLogLevel
|
||||
#endif
|
||||
@@ -79,7 +79,7 @@ class QuasiMinimalResidual : public OperatorFunction<Field> {
|
||||
|
||||
LinOp.Op(x,r); r = b - r;
|
||||
|
||||
assert(normb> 0.0);
|
||||
GRID_ASSERT(normb> 0.0);
|
||||
|
||||
resid = norm2(r)/normb;
|
||||
if (resid <= Tolerance) {
|
||||
@@ -105,8 +105,8 @@ class QuasiMinimalResidual : public OperatorFunction<Field> {
|
||||
for (int i = 1; i <= MaxIterations; i++) {
|
||||
|
||||
// Breakdown tests
|
||||
assert( rho != 0.0);
|
||||
assert( xi != 0.0);
|
||||
GRID_ASSERT( rho != 0.0);
|
||||
GRID_ASSERT( xi != 0.0);
|
||||
|
||||
v = (1. / rho) * v_tld;
|
||||
y = (1. / rho) * y;
|
||||
@@ -134,10 +134,10 @@ class QuasiMinimalResidual : public OperatorFunction<Field> {
|
||||
ep=Zep.real();
|
||||
std::cout << "Zep "<<Zep <<std::endl;
|
||||
// Complex Audit
|
||||
assert(abs(ep)>0);
|
||||
GRID_ASSERT(abs(ep)>0);
|
||||
|
||||
beta = ep / delta;
|
||||
assert(abs(beta)>0);
|
||||
GRID_ASSERT(abs(beta)>0);
|
||||
|
||||
v_tld = p_tld - beta * v;
|
||||
y = v_tld;
|
||||
@@ -158,7 +158,7 @@ class QuasiMinimalResidual : public OperatorFunction<Field> {
|
||||
std::cout << "theta "<<theta<<std::endl;
|
||||
std::cout << "gamma "<<gamma<<std::endl;
|
||||
|
||||
assert(abs(gamma)> 0.0);
|
||||
GRID_ASSERT(abs(gamma)> 0.0);
|
||||
|
||||
eta = -eta * rho_1 * gamma* gamma / (beta * gamma_1 * gamma_1);
|
||||
|
||||
@@ -178,7 +178,7 @@ class QuasiMinimalResidual : public OperatorFunction<Field> {
|
||||
}
|
||||
std::cout << "Iteration "<<i<<" resid " << resid<<std::endl;
|
||||
}
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
return; // no convergence
|
||||
}
|
||||
#else
|
||||
|
||||
@@ -327,9 +327,9 @@ namespace Grid {
|
||||
/////////////////////////////////////////////////////
|
||||
// src_o = (source_o - Moe MeeInv source_e)
|
||||
/////////////////////////////////////////////////////
|
||||
_Matrix.MooeeInv(src_e,tmp); assert( tmp.Checkerboard() ==Even);
|
||||
_Matrix.Meooe (tmp,Mtmp); assert( Mtmp.Checkerboard() ==Odd);
|
||||
tmp=src_o-Mtmp; assert( tmp.Checkerboard() ==Odd);
|
||||
_Matrix.MooeeInv(src_e,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
|
||||
_Matrix.Meooe (tmp,Mtmp); GRID_ASSERT( Mtmp.Checkerboard() ==Odd);
|
||||
tmp=src_o-Mtmp; GRID_ASSERT( tmp.Checkerboard() ==Odd);
|
||||
|
||||
_Matrix.Mooee(tmp,src_o); // Extra factor of "m" in source from dumb choice of matrix norm.
|
||||
}
|
||||
@@ -347,17 +347,17 @@ namespace Grid {
|
||||
///////////////////////////////////////////////////
|
||||
// sol_e = M_ee^-1 * ( src_e - Meo sol_o )...
|
||||
///////////////////////////////////////////////////
|
||||
_Matrix.Meooe(sol_o,tmp); assert( tmp.Checkerboard() ==Even);
|
||||
src_e = src_e-tmp; assert( src_e.Checkerboard() ==Even);
|
||||
_Matrix.MooeeInv(src_e,sol_e); assert( sol_e.Checkerboard() ==Even);
|
||||
_Matrix.Meooe(sol_o,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
|
||||
src_e = src_e-tmp; GRID_ASSERT( src_e.Checkerboard() ==Even);
|
||||
_Matrix.MooeeInv(src_e,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
|
||||
|
||||
setCheckerboard(sol,sol_e); assert( sol_e.Checkerboard() ==Even);
|
||||
setCheckerboard(sol,sol_o); assert( sol_o.Checkerboard() ==Odd );
|
||||
setCheckerboard(sol,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
|
||||
setCheckerboard(sol,sol_o); GRID_ASSERT( sol_o.Checkerboard() ==Odd );
|
||||
}
|
||||
virtual void RedBlackSolve (Matrix & _Matrix,const Field &src_o, Field &sol_o)
|
||||
{
|
||||
SchurStaggeredOperator<Matrix,Field> _HermOpEO(_Matrix);
|
||||
this->_HermitianRBSolver(_HermOpEO,src_o,sol_o); assert(sol_o.Checkerboard()==Odd);
|
||||
this->_HermitianRBSolver(_HermOpEO,src_o,sol_o); GRID_ASSERT(sol_o.Checkerboard()==Odd);
|
||||
};
|
||||
virtual void RedBlackSolve (Matrix & _Matrix,const std::vector<Field> &src_o, std::vector<Field> &sol_o)
|
||||
{
|
||||
@@ -396,13 +396,13 @@ namespace Grid {
|
||||
/////////////////////////////////////////////////////
|
||||
// src_o = Mdag * (source_o - Moe MeeInv source_e)
|
||||
/////////////////////////////////////////////////////
|
||||
_Matrix.MooeeInv(src_e,tmp); assert( tmp.Checkerboard() ==Even);
|
||||
_Matrix.Meooe (tmp,Mtmp); assert( Mtmp.Checkerboard() ==Odd);
|
||||
tmp=src_o-Mtmp; assert( tmp.Checkerboard() ==Odd);
|
||||
_Matrix.MooeeInv(src_e,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
|
||||
_Matrix.Meooe (tmp,Mtmp); GRID_ASSERT( Mtmp.Checkerboard() ==Odd);
|
||||
tmp=src_o-Mtmp; GRID_ASSERT( tmp.Checkerboard() ==Odd);
|
||||
|
||||
// get the right MpcDag
|
||||
SchurDiagMooeeOperator<Matrix,Field> _HermOpEO(_Matrix);
|
||||
_HermOpEO.MpcDag(tmp,src_o); assert(src_o.Checkerboard() ==Odd);
|
||||
_HermOpEO.MpcDag(tmp,src_o); GRID_ASSERT(src_o.Checkerboard() ==Odd);
|
||||
|
||||
}
|
||||
virtual void RedBlackSolution(Matrix & _Matrix,const Field &sol_o, const Field &src_e,Field &sol)
|
||||
@@ -416,17 +416,17 @@ namespace Grid {
|
||||
///////////////////////////////////////////////////
|
||||
// sol_e = M_ee^-1 * ( src_e - Meo sol_o )...
|
||||
///////////////////////////////////////////////////
|
||||
_Matrix.Meooe(sol_o,tmp); assert( tmp.Checkerboard() ==Even);
|
||||
src_e_i = src_e-tmp; assert( src_e_i.Checkerboard() ==Even);
|
||||
_Matrix.MooeeInv(src_e_i,sol_e); assert( sol_e.Checkerboard() ==Even);
|
||||
_Matrix.Meooe(sol_o,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
|
||||
src_e_i = src_e-tmp; GRID_ASSERT( src_e_i.Checkerboard() ==Even);
|
||||
_Matrix.MooeeInv(src_e_i,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
|
||||
|
||||
setCheckerboard(sol,sol_e); assert( sol_e.Checkerboard() ==Even);
|
||||
setCheckerboard(sol,sol_o); assert( sol_o.Checkerboard() ==Odd );
|
||||
setCheckerboard(sol,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
|
||||
setCheckerboard(sol,sol_o); GRID_ASSERT( sol_o.Checkerboard() ==Odd );
|
||||
}
|
||||
virtual void RedBlackSolve (Matrix & _Matrix,const Field &src_o, Field &sol_o)
|
||||
{
|
||||
SchurDiagMooeeOperator<Matrix,Field> _HermOpEO(_Matrix);
|
||||
this->_HermitianRBSolver(_HermOpEO,src_o,sol_o); assert(sol_o.Checkerboard()==Odd);
|
||||
this->_HermitianRBSolver(_HermOpEO,src_o,sol_o); GRID_ASSERT(sol_o.Checkerboard()==Odd);
|
||||
};
|
||||
virtual void RedBlackSolve (Matrix & _Matrix,const std::vector<Field> &src_o, std::vector<Field> &sol_o)
|
||||
{
|
||||
@@ -461,9 +461,9 @@ namespace Grid {
|
||||
/////////////////////////////////////////////////////
|
||||
// src_o = Mdag * (source_o - Moe MeeInv source_e)
|
||||
/////////////////////////////////////////////////////
|
||||
_Matrix.MooeeInv(src_e, tmp); assert( tmp.Checkerboard() == Even );
|
||||
_Matrix.Meooe (tmp, Mtmp); assert( Mtmp.Checkerboard() == Odd );
|
||||
src_o -= Mtmp; assert( src_o.Checkerboard() == Odd );
|
||||
_Matrix.MooeeInv(src_e, tmp); GRID_ASSERT( tmp.Checkerboard() == Even );
|
||||
_Matrix.Meooe (tmp, Mtmp); GRID_ASSERT( Mtmp.Checkerboard() == Odd );
|
||||
src_o -= Mtmp; GRID_ASSERT( src_o.Checkerboard() == Odd );
|
||||
}
|
||||
|
||||
virtual void RedBlackSolution(Matrix& _Matrix, const Field& sol_o, const Field& src_e, Field& sol)
|
||||
@@ -478,18 +478,18 @@ namespace Grid {
|
||||
///////////////////////////////////////////////////
|
||||
// sol_e = M_ee^-1 * ( src_e - Meo sol_o )...
|
||||
///////////////////////////////////////////////////
|
||||
_Matrix.Meooe(sol_o, tmp); assert( tmp.Checkerboard() == Even );
|
||||
src_e_i = src_e - tmp; assert( src_e_i.Checkerboard() == Even );
|
||||
_Matrix.MooeeInv(src_e_i, sol_e); assert( sol_e.Checkerboard() == Even );
|
||||
_Matrix.Meooe(sol_o, tmp); GRID_ASSERT( tmp.Checkerboard() == Even );
|
||||
src_e_i = src_e - tmp; GRID_ASSERT( src_e_i.Checkerboard() == Even );
|
||||
_Matrix.MooeeInv(src_e_i, sol_e); GRID_ASSERT( sol_e.Checkerboard() == Even );
|
||||
|
||||
setCheckerboard(sol, sol_e); assert( sol_e.Checkerboard() == Even );
|
||||
setCheckerboard(sol, sol_o); assert( sol_o.Checkerboard() == Odd );
|
||||
setCheckerboard(sol, sol_e); GRID_ASSERT( sol_e.Checkerboard() == Even );
|
||||
setCheckerboard(sol, sol_o); GRID_ASSERT( sol_o.Checkerboard() == Odd );
|
||||
}
|
||||
|
||||
virtual void RedBlackSolve(Matrix& _Matrix, const Field& src_o, Field& sol_o)
|
||||
{
|
||||
NonHermitianSchurDiagMooeeOperator<Matrix,Field> _OpEO(_Matrix);
|
||||
this->_HermitianRBSolver(_OpEO, src_o, sol_o); assert(sol_o.Checkerboard() == Odd);
|
||||
this->_HermitianRBSolver(_OpEO, src_o, sol_o); GRID_ASSERT(sol_o.Checkerboard() == Odd);
|
||||
}
|
||||
|
||||
virtual void RedBlackSolve(Matrix& _Matrix, const std::vector<Field>& src_o, std::vector<Field>& sol_o)
|
||||
@@ -499,6 +499,87 @@ namespace Grid {
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Site diagonal is identity, left preconditioned by Mee^inv
|
||||
// ( 1 - Mee^inv Meo Moo^inv Moe ) phi = Mee_inv ( Mee - Meo Moo^inv Moe Mee^inv ) phi = Mee_inv eta
|
||||
//
|
||||
// Solve:
|
||||
// ( 1 - Mee^inv Meo Moo^inv Moe )^dag ( 1 - Mee^inv Meo Moo^inv Moe ) phi = ( 1 - Mee^inv Meo Moo^inv Moe )^dag Mee_inv eta
|
||||
//
|
||||
// Old notation e<->o
|
||||
//
|
||||
// Left precon by Moo^-1
|
||||
// b) (Doo^{dag} M_oo^-dag) (Moo^-1 Doo) psi_o = [ (D_oo)^dag M_oo^-dag ] Moo^-1 L^{-1} eta_o
|
||||
// eta_o' = (D_oo)^dag M_oo^-dag Moo^-1 (eta_o - Moe Mee^{-1} eta_e)
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
template<class Field> class SchurRedBlackDiagOneSolve : public SchurRedBlackBase<Field> {
|
||||
public:
|
||||
typedef CheckerBoardedSparseMatrixBase<Field> Matrix;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// Wrap the usual normal equations Schur trick
|
||||
/////////////////////////////////////////////////////
|
||||
SchurRedBlackDiagOneSolve(OperatorFunction<Field> &HermitianRBSolver, const bool initSubGuess = false,
|
||||
const bool _solnAsInitGuess = false)
|
||||
: SchurRedBlackBase<Field>(HermitianRBSolver,initSubGuess,_solnAsInitGuess) {};
|
||||
|
||||
virtual void RedBlackSource(Matrix & _Matrix,const Field &src, Field &src_e,Field &src_o)
|
||||
{
|
||||
GridBase *grid = _Matrix.RedBlackGrid();
|
||||
GridBase *fgrid= _Matrix.Grid();
|
||||
|
||||
SchurDiagOneOperator<Matrix,Field> _HermOpEO(_Matrix);
|
||||
|
||||
Field tmp(grid);
|
||||
Field Mtmp(grid);
|
||||
|
||||
pickCheckerboard(Even,src_e,src);
|
||||
pickCheckerboard(Odd ,src_o,src);
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
// src_o = Mpcdag *MooeeInv * (source_o - Moe MeeInv source_e)
|
||||
/////////////////////////////////////////////////////
|
||||
_Matrix.MooeeInv(src_e,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
|
||||
_Matrix.Meooe (tmp,Mtmp); GRID_ASSERT( Mtmp.Checkerboard() ==Odd);
|
||||
Mtmp=src_o-Mtmp;
|
||||
_Matrix.MooeeInv(Mtmp,tmp); GRID_ASSERT( tmp.Checkerboard() ==Odd);
|
||||
|
||||
// get the right MpcDag
|
||||
_HermOpEO.MpcDag(tmp,src_o); GRID_ASSERT(src_o.Checkerboard() ==Odd);
|
||||
}
|
||||
|
||||
virtual void RedBlackSolution(Matrix & _Matrix,const Field &sol_o, const Field &src_e,Field &sol)
|
||||
{
|
||||
GridBase *grid = _Matrix.RedBlackGrid();
|
||||
GridBase *fgrid= _Matrix.Grid();
|
||||
|
||||
Field tmp(grid);
|
||||
Field sol_e(grid);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// sol_e = M_ee^-1 * ( src_e - Meo sol_o )...
|
||||
///////////////////////////////////////////////////
|
||||
_Matrix.Meooe(sol_o,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
|
||||
tmp = src_e-tmp; GRID_ASSERT( src_e.Checkerboard() ==Even);
|
||||
_Matrix.MooeeInv(tmp,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
|
||||
|
||||
setCheckerboard(sol,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
|
||||
setCheckerboard(sol,sol_o); GRID_ASSERT( sol_o.Checkerboard() ==Odd );
|
||||
};
|
||||
|
||||
virtual void RedBlackSolve (Matrix & _Matrix,const Field &src_o, Field &sol_o)
|
||||
{
|
||||
SchurDiagOneOperator<Matrix,Field> _HermOpEO(_Matrix);
|
||||
this->_HermitianRBSolver(_HermOpEO,src_o,sol_o);
|
||||
};
|
||||
virtual void RedBlackSolve (Matrix & _Matrix,const std::vector<Field> &src_o, std::vector<Field> &sol_o)
|
||||
{
|
||||
SchurDiagOneOperator<Matrix,Field> _HermOpEO(_Matrix);
|
||||
this->_HermitianRBSolver(_HermOpEO,src_o,sol_o);
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Site diagonal is identity, right preconditioned by Mee^inv
|
||||
// ( 1 - Meo Moo^inv Moe Mee^inv ) phi =( 1 - Meo Moo^inv Moe Mee^inv ) Mee psi = = eta = eta
|
||||
@@ -531,12 +612,12 @@ namespace Grid {
|
||||
/////////////////////////////////////////////////////
|
||||
// src_o = Mdag * (source_o - Moe MeeInv source_e)
|
||||
/////////////////////////////////////////////////////
|
||||
_Matrix.MooeeInv(src_e,tmp); assert( tmp.Checkerboard() ==Even);
|
||||
_Matrix.Meooe (tmp,Mtmp); assert( Mtmp.Checkerboard() ==Odd);
|
||||
tmp=src_o-Mtmp; assert( tmp.Checkerboard() ==Odd);
|
||||
_Matrix.MooeeInv(src_e,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
|
||||
_Matrix.Meooe (tmp,Mtmp); GRID_ASSERT( Mtmp.Checkerboard() ==Odd);
|
||||
tmp=src_o-Mtmp; GRID_ASSERT( tmp.Checkerboard() ==Odd);
|
||||
|
||||
// get the right MpcDag
|
||||
_HermOpEO.MpcDag(tmp,src_o); assert(src_o.Checkerboard() ==Odd);
|
||||
_HermOpEO.MpcDag(tmp,src_o); GRID_ASSERT(src_o.Checkerboard() ==Odd);
|
||||
}
|
||||
|
||||
virtual void RedBlackSolution(Matrix & _Matrix,const Field &sol_o, const Field &src_e,Field &sol)
|
||||
@@ -557,12 +638,12 @@ namespace Grid {
|
||||
///////////////////////////////////////////////////
|
||||
// sol_e = M_ee^-1 * ( src_e - Meo sol_o )...
|
||||
///////////////////////////////////////////////////
|
||||
_Matrix.Meooe(sol_o_i,tmp); assert( tmp.Checkerboard() ==Even);
|
||||
tmp = src_e-tmp; assert( src_e.Checkerboard() ==Even);
|
||||
_Matrix.MooeeInv(tmp,sol_e); assert( sol_e.Checkerboard() ==Even);
|
||||
_Matrix.Meooe(sol_o_i,tmp); GRID_ASSERT( tmp.Checkerboard() ==Even);
|
||||
tmp = src_e-tmp; GRID_ASSERT( src_e.Checkerboard() ==Even);
|
||||
_Matrix.MooeeInv(tmp,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
|
||||
|
||||
setCheckerboard(sol,sol_e); assert( sol_e.Checkerboard() ==Even);
|
||||
setCheckerboard(sol,sol_o_i); assert( sol_o_i.Checkerboard() ==Odd );
|
||||
setCheckerboard(sol,sol_e); GRID_ASSERT( sol_e.Checkerboard() ==Even);
|
||||
setCheckerboard(sol,sol_o_i); GRID_ASSERT( sol_o_i.Checkerboard() ==Odd );
|
||||
};
|
||||
|
||||
virtual void RedBlackSolve (Matrix & _Matrix,const Field &src_o, Field &sol_o)
|
||||
@@ -603,9 +684,9 @@ namespace Grid {
|
||||
/////////////////////////////////////////////////////
|
||||
// src_o = Mdag * (source_o - Moe MeeInv source_e)
|
||||
/////////////////////////////////////////////////////
|
||||
_Matrix.MooeeInv(src_e, tmp); assert( tmp.Checkerboard() == Even );
|
||||
_Matrix.Meooe (tmp, Mtmp); assert( Mtmp.Checkerboard() == Odd );
|
||||
src_o -= Mtmp; assert( src_o.Checkerboard() == Odd );
|
||||
_Matrix.MooeeInv(src_e, tmp); GRID_ASSERT( tmp.Checkerboard() == Even );
|
||||
_Matrix.Meooe (tmp, Mtmp); GRID_ASSERT( Mtmp.Checkerboard() == Odd );
|
||||
src_o -= Mtmp; GRID_ASSERT( src_o.Checkerboard() == Odd );
|
||||
}
|
||||
|
||||
virtual void RedBlackSolution(Matrix& _Matrix, const Field& sol_o, const Field& src_e, Field& sol)
|
||||
@@ -626,12 +707,12 @@ namespace Grid {
|
||||
///////////////////////////////////////////////////
|
||||
// sol_e = M_ee^-1 * ( src_e - Meo sol_o )...
|
||||
///////////////////////////////////////////////////
|
||||
_Matrix.Meooe(sol_o_i, tmp); assert( tmp.Checkerboard() == Even );
|
||||
tmp = src_e - tmp; assert( src_e.Checkerboard() == Even );
|
||||
_Matrix.MooeeInv(tmp, sol_e); assert( sol_e.Checkerboard() == Even );
|
||||
_Matrix.Meooe(sol_o_i, tmp); GRID_ASSERT( tmp.Checkerboard() == Even );
|
||||
tmp = src_e - tmp; GRID_ASSERT( src_e.Checkerboard() == Even );
|
||||
_Matrix.MooeeInv(tmp, sol_e); GRID_ASSERT( sol_e.Checkerboard() == Even );
|
||||
|
||||
setCheckerboard(sol, sol_e); assert( sol_e.Checkerboard() == Even );
|
||||
setCheckerboard(sol, sol_o_i); assert( sol_o_i.Checkerboard() == Odd );
|
||||
setCheckerboard(sol, sol_e); GRID_ASSERT( sol_e.Checkerboard() == Even );
|
||||
setCheckerboard(sol, sol_o_i); GRID_ASSERT( sol_o_i.Checkerboard() == Odd );
|
||||
};
|
||||
|
||||
virtual void RedBlackSolve(Matrix& _Matrix, const Field& src_o, Field& sol_o)
|
||||
|
||||
@@ -0,0 +1,631 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./lib/algorithms/Aggregates.h
|
||||
|
||||
Copyright (C) 2015
|
||||
|
||||
Author: Azusa Yamaguchi <ayamaguc@staffmail.ed.ac.uk>
|
||||
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
|
||||
Author: Peter Boyle <peterboyle@Peters-MacBook-Pro-2.local>
|
||||
Author: paboyle <paboyle@ph.ed.ac.uk>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
#include <Grid/algorithms/iterative/PrecGeneralisedConjugateResidualNonHermitian.h>
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
inline RealD AggregatePowerLaw(RealD x)
|
||||
{
|
||||
// return std::pow(x,-4);
|
||||
// return std::pow(x,-3);
|
||||
return std::pow(x,-5);
|
||||
}
|
||||
|
||||
template<class Fobj,class CComplex,int nbasis>
|
||||
class Aggregation {
|
||||
public:
|
||||
constexpr int Nbasis(void) { return nbasis; };
|
||||
|
||||
typedef iVector<CComplex,nbasis > siteVector;
|
||||
typedef Lattice<siteVector> CoarseVector;
|
||||
typedef Lattice<iMatrix<CComplex,nbasis > > CoarseMatrix;
|
||||
|
||||
typedef Lattice< CComplex > CoarseScalar; // used for inner products on fine field
|
||||
typedef Lattice<Fobj > FineField;
|
||||
|
||||
GridBase *CoarseGrid;
|
||||
GridBase *FineGrid;
|
||||
std::vector<Lattice<Fobj> > subspace;
|
||||
int checkerboard;
|
||||
int Checkerboard(void){return checkerboard;}
|
||||
Aggregation(GridBase *_CoarseGrid,GridBase *_FineGrid,int _checkerboard) :
|
||||
CoarseGrid(_CoarseGrid),
|
||||
FineGrid(_FineGrid),
|
||||
subspace(nbasis,_FineGrid),
|
||||
checkerboard(_checkerboard)
|
||||
{
|
||||
};
|
||||
|
||||
void GlobalOrthonormalise(void)
|
||||
{
|
||||
// Normalise all vectors
|
||||
for(int i=0;i<nbasis; i++){
|
||||
RealD scale = std::pow(norm2(subspace[i]),-0.5);
|
||||
subspace[i] = subspace[i]*scale;
|
||||
}
|
||||
for(int i=0;i<nbasis; i++){
|
||||
for(int j=0;j<i; j++){
|
||||
basisOrthogonalize(subspace,subspace[i],j);
|
||||
}
|
||||
RealD scale = std::pow(norm2(subspace[i]),-0.5);
|
||||
subspace[i] = subspace[i]*scale;
|
||||
}
|
||||
}
|
||||
void Orthogonalise(void){
|
||||
CoarseScalar InnerProd(CoarseGrid);
|
||||
// std::cout << GridLogMessage <<" Block Gramm-Schmidt pass 1"<<std::endl;
|
||||
blockOrthogonalise(InnerProd,subspace);
|
||||
}
|
||||
void ProjectToSubspace(CoarseVector &CoarseVec,const FineField &FineVec){
|
||||
blockProject(CoarseVec,FineVec,subspace);
|
||||
}
|
||||
void PromoteFromSubspace(const CoarseVector &CoarseVec,FineField &FineVec){
|
||||
FineVec.Checkerboard() = subspace[0].Checkerboard();
|
||||
blockPromote(CoarseVec,FineVec,subspace);
|
||||
}
|
||||
|
||||
virtual void CreateSubspaceRandom(GridParallelRNG &RNG) {
|
||||
int nn=nbasis;
|
||||
RealD scale;
|
||||
FineField noise(FineGrid);
|
||||
for(int b=0;b<nn;b++){
|
||||
subspace[b] = Zero();
|
||||
gaussian(RNG,noise);
|
||||
scale = std::pow(norm2(noise),-0.5);
|
||||
noise=noise*scale;
|
||||
subspace[b] = noise;
|
||||
}
|
||||
}
|
||||
virtual void CreateSubspace(GridParallelRNG &RNG,LinearOperatorBase<FineField> &hermop,int nn=nbasis)
|
||||
{
|
||||
|
||||
RealD scale;
|
||||
|
||||
ConjugateGradient<FineField> CG(1.0e-4,2000,false);
|
||||
FineField noise(FineGrid);
|
||||
FineField Mn(FineGrid);
|
||||
|
||||
for(int b=0;b<nn;b++){
|
||||
|
||||
subspace[b] = Zero();
|
||||
gaussian(RNG,noise);
|
||||
scale = std::pow(norm2(noise),-0.5);
|
||||
noise=noise*scale;
|
||||
|
||||
hermop.Op(noise,Mn); std::cout<<GridLogMessage << "noise ["<<b<<"] <n|MdagM|n> "<<norm2(Mn)<<std::endl;
|
||||
|
||||
for(int i=0;i<2;i++){
|
||||
|
||||
CG(hermop,noise,subspace[b]);
|
||||
|
||||
noise = subspace[b];
|
||||
scale = std::pow(norm2(noise),-0.5);
|
||||
noise=noise*scale;
|
||||
|
||||
hermop.Op(noise,Mn); std::cout<<GridLogMessage << "intermediate["<<i<<"] <i|MdagM|i> "<<norm2(Mn)<<std::endl;
|
||||
|
||||
}
|
||||
|
||||
hermop.Op(noise,Mn); std::cout<<GridLogMessage << "filtered["<<b<<"] <f|MdagM|f> "<<norm2(Mn)<<std::endl;
|
||||
subspace[b] = noise;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
virtual void CreateSubspaceGCR(GridParallelRNG &RNG,LinearOperatorBase<FineField> &DiracOp,int nn=nbasis)
|
||||
{
|
||||
RealD scale;
|
||||
|
||||
TrivialPrecon<FineField> simple_fine;
|
||||
// PrecGeneralisedConjugateResidualNonHermitian<FineField> GCR(0.001,10,DiracOp,simple_fine,30,30);
|
||||
// PrecGeneralisedConjugateResidualNonHermitian<FineField> GCR(0.001,10,DiracOp,simple_fine,12,12);
|
||||
// PrecGeneralisedConjugateResidualNonHermitian<FineField> GCR(0.001,30,DiracOp,simple_fine,12,12);
|
||||
// PrecGeneralisedConjugateResidualNonHermitian<FineField> GCR(0.0005,30,DiracOp,simple_fine,20,20);
|
||||
PrecGeneralisedConjugateResidualNonHermitian<FineField> GCR(0.0005,30,DiracOp,simple_fine,10,10);
|
||||
FineField noise(FineGrid);
|
||||
FineField src(FineGrid);
|
||||
FineField guess(FineGrid);
|
||||
FineField Mn(FineGrid);
|
||||
|
||||
for(int b=0;b<nn;b++){
|
||||
|
||||
subspace[b] = Zero();
|
||||
gaussian(RNG,noise);
|
||||
scale = std::pow(norm2(noise),-0.5);
|
||||
noise=noise*scale;
|
||||
|
||||
DiracOp.Op(noise,Mn); std::cout<<GridLogMessage << "noise ["<<b<<"] <n|Op|n> "<<innerProduct(noise,Mn)<<std::endl;
|
||||
|
||||
for(int i=0;i<3;i++){
|
||||
// void operator() (const Field &src, Field &psi){
|
||||
#if 1
|
||||
if (i==0)std::cout << GridLogMessage << " inverting on noise "<<std::endl;
|
||||
src = noise;
|
||||
guess=Zero();
|
||||
GCR(src,guess);
|
||||
subspace[b] = guess;
|
||||
#else
|
||||
if (i==0)std::cout << GridLogMessage << " inverting on zero "<<std::endl;
|
||||
src=Zero();
|
||||
guess = noise;
|
||||
GCR(src,guess);
|
||||
subspace[b] = guess;
|
||||
#endif
|
||||
noise = subspace[b];
|
||||
scale = std::pow(norm2(noise),-0.5);
|
||||
noise=noise*scale;
|
||||
|
||||
DiracOp.Op(noise,Mn); std::cout<<GridLogMessage << "intermediate["<<i<<"] <f|Op|f> "<<innerProduct(noise,Mn)<<" <f|OpDagOp|f>"<<norm2(Mn)<<std::endl;
|
||||
|
||||
}
|
||||
|
||||
DiracOp.Op(noise,Mn); std::cout<<GridLogMessage << "filtered["<<b<<"] <f|Op|f> "<<innerProduct(noise,Mn)<<" <f|OpDagOp|f>"<<norm2(Mn)<<std::endl;
|
||||
subspace[b] = noise;
|
||||
|
||||
}
|
||||
GlobalOrthonormalise();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// World of possibilities here. But have tried quite a lot of experiments (250+ jobs run on Summit)
|
||||
// and this is the best I found
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
virtual void CreateSubspaceChebyshev(GridParallelRNG &RNG,LinearOperatorBase<FineField> &hermop,
|
||||
int nn,
|
||||
double hi,
|
||||
double lo,
|
||||
int orderfilter,
|
||||
int ordermin,
|
||||
int orderstep,
|
||||
double filterlo
|
||||
) {
|
||||
|
||||
RealD scale;
|
||||
|
||||
FineField noise(FineGrid);
|
||||
FineField Mn(FineGrid);
|
||||
FineField tmp(FineGrid);
|
||||
|
||||
// New normalised noise
|
||||
gaussian(RNG,noise);
|
||||
scale = std::pow(norm2(noise),-0.5);
|
||||
noise=noise*scale;
|
||||
|
||||
std::cout << GridLogMessage<<" Chebyshev subspace pass-1 : ord "<<orderfilter<<" ["<<lo<<","<<hi<<"]"<<std::endl;
|
||||
std::cout << GridLogMessage<<" Chebyshev subspace pass-2 : nbasis"<<nn<<" min "
|
||||
<<ordermin<<" step "<<orderstep
|
||||
<<" lo"<<filterlo<<std::endl;
|
||||
|
||||
// Initial matrix element
|
||||
hermop.Op(noise,Mn); std::cout<<GridLogMessage << "noise <n|MdagM|n> "<<norm2(Mn)<<std::endl;
|
||||
|
||||
int b =0;
|
||||
{
|
||||
ComplexD ip;
|
||||
// Filter
|
||||
Chebyshev<FineField> Cheb(lo,hi,orderfilter);
|
||||
Cheb(hermop,noise,Mn);
|
||||
// normalise
|
||||
scale = std::pow(norm2(Mn),-0.5); Mn=Mn*scale;
|
||||
subspace[b] = Mn;
|
||||
|
||||
hermop.Op(Mn,tmp);
|
||||
ip= innerProduct(Mn,tmp);
|
||||
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|Op|n> "<<norm2(tmp)<<" "<<ip<<std::endl;
|
||||
|
||||
hermop.AdjOp(Mn,tmp);
|
||||
ip = innerProduct(Mn,tmp);
|
||||
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|AdjOp|n> "<<norm2(tmp)<<" "<<ip<<std::endl;
|
||||
b++;
|
||||
}
|
||||
|
||||
// Generate a full sequence of Chebyshevs
|
||||
{
|
||||
lo=filterlo;
|
||||
noise=Mn;
|
||||
|
||||
FineField T0(FineGrid); T0 = noise;
|
||||
FineField T1(FineGrid);
|
||||
FineField T2(FineGrid);
|
||||
FineField y(FineGrid);
|
||||
|
||||
FineField *Tnm = &T0;
|
||||
FineField *Tn = &T1;
|
||||
FineField *Tnp = &T2;
|
||||
|
||||
// Tn=T1 = (xscale M + mscale)in
|
||||
RealD xscale = 2.0/(hi-lo);
|
||||
RealD mscale = -(hi+lo)/(hi-lo);
|
||||
hermop.HermOp(T0,y);
|
||||
T1=y*xscale+noise*mscale;
|
||||
|
||||
for(int n=2;n<=ordermin+orderstep*(nn-2);n++){
|
||||
|
||||
hermop.HermOp(*Tn,y);
|
||||
|
||||
autoView( y_v , y, AcceleratorWrite);
|
||||
autoView( Tn_v , (*Tn), AcceleratorWrite);
|
||||
autoView( Tnp_v , (*Tnp), AcceleratorWrite);
|
||||
autoView( Tnm_v , (*Tnm), AcceleratorWrite);
|
||||
const int Nsimd = CComplex::Nsimd();
|
||||
accelerator_for(ss, FineGrid->oSites(), Nsimd, {
|
||||
coalescedWrite(y_v[ss],xscale*y_v(ss)+mscale*Tn_v(ss));
|
||||
coalescedWrite(Tnp_v[ss],2.0*y_v(ss)-Tnm_v(ss));
|
||||
});
|
||||
|
||||
// Possible more fine grained control is needed than a linear sweep,
|
||||
// but huge productivity gain if this is simple algorithm and not a tunable
|
||||
int m =1;
|
||||
if ( n>=ordermin ) m=n-ordermin;
|
||||
if ( (m%orderstep)==0 ) {
|
||||
Mn=*Tnp;
|
||||
scale = std::pow(norm2(Mn),-0.5); Mn=Mn*scale;
|
||||
subspace[b] = Mn;
|
||||
|
||||
|
||||
ComplexD ip;
|
||||
|
||||
hermop.Op(Mn,tmp);
|
||||
ip= innerProduct(Mn,tmp);
|
||||
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|Op|n> "<<norm2(tmp)<<" "<<ip<<std::endl;
|
||||
|
||||
hermop.AdjOp(Mn,tmp);
|
||||
ip = innerProduct(Mn,tmp);
|
||||
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|AdjOp|n> "<<norm2(tmp)<<" "<<ip<<std::endl;
|
||||
|
||||
b++;
|
||||
}
|
||||
|
||||
// Cycle pointers to avoid copies
|
||||
FineField *swizzle = Tnm;
|
||||
Tnm =Tn;
|
||||
Tn =Tnp;
|
||||
Tnp =swizzle;
|
||||
|
||||
}
|
||||
}
|
||||
GRID_ASSERT(b==nn);
|
||||
}
|
||||
|
||||
|
||||
virtual void CreateSubspacePolyCheby(GridParallelRNG &RNG,LinearOperatorBase<FineField> &hermop,
|
||||
int nn,
|
||||
double hi,
|
||||
double lo1,
|
||||
int orderfilter,
|
||||
double lo2,
|
||||
int orderstep)
|
||||
{
|
||||
RealD scale;
|
||||
|
||||
FineField noise(FineGrid);
|
||||
FineField Mn(FineGrid);
|
||||
FineField tmp(FineGrid);
|
||||
|
||||
// New normalised noise
|
||||
gaussian(RNG,noise);
|
||||
scale = std::pow(norm2(noise),-0.5);
|
||||
noise=noise*scale;
|
||||
|
||||
std::cout << GridLogMessage<<" CreateSubspacePolyCheby "<<std::endl;
|
||||
// Initial matrix element
|
||||
hermop.Op(noise,Mn);
|
||||
std::cout<<GridLogMessage << "noise <n|MdagM|n> "<<norm2(Mn)<<std::endl;
|
||||
|
||||
int b =0;
|
||||
{
|
||||
// Filter
|
||||
std::cout << GridLogMessage << "Cheby "<<lo1<<","<<hi<<" "<<orderstep<<std::endl;
|
||||
Chebyshev<FineField> Cheb(lo1,hi,orderfilter);
|
||||
Cheb(hermop,noise,Mn);
|
||||
// normalise
|
||||
scale = std::pow(norm2(Mn),-0.5); Mn=Mn*scale;
|
||||
subspace[b] = Mn;
|
||||
hermop.Op(Mn,tmp);
|
||||
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|MdagM|n> "<<norm2(tmp)<<std::endl;
|
||||
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|n> "<<norm2(Mn)<<std::endl;
|
||||
}
|
||||
|
||||
// Generate a full sequence of Chebyshevs
|
||||
for(int n=1;n<nn;n++){
|
||||
std::cout << GridLogMessage << "Cheby "<<lo2<<","<<hi<<" "<<orderstep<<std::endl;
|
||||
Chebyshev<FineField> Cheb(lo2,hi,orderstep);
|
||||
Cheb(hermop,subspace[n-1],Mn);
|
||||
|
||||
for(int m=0;m<n;m++){
|
||||
ComplexD c = innerProduct(subspace[m],Mn);
|
||||
Mn = Mn - c*subspace[m];
|
||||
}
|
||||
|
||||
// normalise
|
||||
scale = std::pow(norm2(Mn),-0.5);
|
||||
Mn=Mn*scale;
|
||||
|
||||
subspace[n]=Mn;
|
||||
|
||||
hermop.Op(Mn,tmp);
|
||||
std::cout<<GridLogMessage << "filt ["<<n<<"] <n|MdagM|n> "<<norm2(tmp)<<std::endl;
|
||||
std::cout<<GridLogMessage << "filt ["<<n<<"] <n|n> "<<norm2(Mn)<<std::endl;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
virtual void CreateSubspaceChebyshev(GridParallelRNG &RNG,LinearOperatorBase<FineField> &hermop,
|
||||
int nn,
|
||||
double hi,
|
||||
double lo,
|
||||
int orderfilter
|
||||
) {
|
||||
|
||||
RealD scale;
|
||||
|
||||
FineField noise(FineGrid);
|
||||
FineField Mn(FineGrid);
|
||||
FineField tmp(FineGrid);
|
||||
|
||||
// New normalised noise
|
||||
std::cout << GridLogMessage<<" Chebyshev subspace pure noise : ord "<<orderfilter<<" ["<<lo<<","<<hi<<"]"<<std::endl;
|
||||
std::cout << GridLogMessage<<" Chebyshev subspace pure noise : nbasis "<<nn<<std::endl;
|
||||
|
||||
|
||||
for(int b =0;b<nbasis;b++)
|
||||
{
|
||||
gaussian(RNG,noise);
|
||||
scale = std::pow(norm2(noise),-0.5);
|
||||
noise=noise*scale;
|
||||
|
||||
// Initial matrix element
|
||||
hermop.Op(noise,Mn);
|
||||
if(b==0) std::cout<<GridLogMessage << "noise <n|MdagM|n> "<<norm2(Mn)<<std::endl;
|
||||
|
||||
// Filter
|
||||
Chebyshev<FineField> Cheb(lo,hi,orderfilter);
|
||||
Cheb(hermop,noise,Mn);
|
||||
scale = std::pow(norm2(Mn),-0.5); Mn=Mn*scale;
|
||||
|
||||
// Refine
|
||||
Chebyshev<FineField> PowerLaw(lo,hi,1000,AggregatePowerLaw);
|
||||
noise = Mn;
|
||||
PowerLaw(hermop,noise,Mn);
|
||||
scale = std::pow(norm2(Mn),-0.5); Mn=Mn*scale;
|
||||
|
||||
// normalise
|
||||
subspace[b] = Mn;
|
||||
hermop.Op(Mn,tmp);
|
||||
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|MdagM|n> "<<norm2(tmp)<<std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
virtual void CreateSubspaceChebyshevPowerLaw(GridParallelRNG &RNG,LinearOperatorBase<FineField> &hermop,
|
||||
int nn,
|
||||
double hi,
|
||||
int orderfilter
|
||||
) {
|
||||
|
||||
RealD scale;
|
||||
|
||||
FineField noise(FineGrid);
|
||||
FineField Mn(FineGrid);
|
||||
FineField tmp(FineGrid);
|
||||
|
||||
// New normalised noise
|
||||
std::cout << GridLogMessage<<" Chebyshev subspace pure noise : ord "<<orderfilter<<" [0,"<<hi<<"]"<<std::endl;
|
||||
std::cout << GridLogMessage<<" Chebyshev subspace pure noise : nbasis "<<nn<<std::endl;
|
||||
|
||||
for(int b =0;b<nbasis;b++)
|
||||
{
|
||||
gaussian(RNG,noise);
|
||||
scale = std::pow(norm2(noise),-0.5);
|
||||
noise=noise*scale;
|
||||
|
||||
// Initial matrix element
|
||||
hermop.Op(noise,Mn);
|
||||
if(b==0) std::cout<<GridLogMessage << "noise <n|MdagM|n> "<<norm2(Mn)<<std::endl;
|
||||
// Filter
|
||||
Chebyshev<FineField> Cheb(0.0,hi,orderfilter,AggregatePowerLaw);
|
||||
Cheb(hermop,noise,Mn);
|
||||
// normalise
|
||||
scale = std::pow(norm2(Mn),-0.5); Mn=Mn*scale;
|
||||
subspace[b] = Mn;
|
||||
hermop.Op(Mn,tmp);
|
||||
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|MdagM|n> "<<norm2(tmp)<<std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
virtual void CreateSubspaceChebyshevNew(GridParallelRNG &RNG,LinearOperatorBase<FineField> &hermop,
|
||||
double hi
|
||||
) {
|
||||
|
||||
RealD scale;
|
||||
|
||||
FineField noise(FineGrid);
|
||||
FineField Mn(FineGrid);
|
||||
FineField tmp(FineGrid);
|
||||
|
||||
// New normalised noise
|
||||
for(int b =0;b<nbasis;b++)
|
||||
{
|
||||
gaussian(RNG,noise);
|
||||
scale = std::pow(norm2(noise),-0.5);
|
||||
noise=noise*scale;
|
||||
|
||||
// Initial matrix element
|
||||
hermop.Op(noise,Mn);
|
||||
if(b==0) std::cout<<GridLogMessage << "noise <n|MdagM|n> "<<norm2(Mn)<<std::endl;
|
||||
// Filter
|
||||
//#opt2(x) = acheb(x,3,90,300)* acheb(x,1,90,50) * acheb(x,0.5,90,200) * acheb(x,0.05,90,400) * acheb(x,0.01,90,1500)
|
||||
/*266
|
||||
Chebyshev<FineField> Cheb1(3.0,hi,300);
|
||||
Chebyshev<FineField> Cheb2(1.0,hi,50);
|
||||
Chebyshev<FineField> Cheb3(0.5,hi,300);
|
||||
Chebyshev<FineField> Cheb4(0.05,hi,500);
|
||||
Chebyshev<FineField> Cheb5(0.01,hi,2000);
|
||||
*/
|
||||
/* 242 */
|
||||
/*
|
||||
Chebyshev<FineField> Cheb3(0.1,hi,300);
|
||||
Chebyshev<FineField> Cheb2(0.02,hi,1000);
|
||||
Chebyshev<FineField> Cheb1(0.003,hi,2000);
|
||||
8?
|
||||
*/
|
||||
/* How many??
|
||||
*/
|
||||
Chebyshev<FineField> Cheb2(0.001,hi,2500); // 169 iters on HDCG after refine
|
||||
Chebyshev<FineField> Cheb1(0.02,hi,600);
|
||||
|
||||
// Chebyshev<FineField> Cheb2(0.001,hi,1500);
|
||||
// Chebyshev<FineField> Cheb1(0.02,hi,600);
|
||||
Cheb1(hermop,noise,Mn); scale = std::pow(norm2(Mn),-0.5); noise=Mn*scale;
|
||||
hermop.Op(noise,tmp); std::cout<<GridLogMessage << "Cheb1 <n|MdagM|n> "<<norm2(tmp)<<std::endl;
|
||||
Cheb2(hermop,noise,Mn); scale = std::pow(norm2(Mn),-0.5); noise=Mn*scale;
|
||||
hermop.Op(noise,tmp); std::cout<<GridLogMessage << "Cheb2 <n|MdagM|n> "<<norm2(tmp)<<std::endl;
|
||||
// Cheb3(hermop,noise,Mn); scale = std::pow(norm2(Mn),-0.5); noise=Mn*scale;
|
||||
// hermop.Op(noise,tmp); std::cout<<GridLogMessage << "Cheb3 <n|MdagM|n> "<<norm2(tmp)<<std::endl;
|
||||
// Cheb4(hermop,noise,Mn); scale = std::pow(norm2(Mn),-0.5); noise=Mn*scale;
|
||||
// hermop.Op(noise,tmp); std::cout<<GridLogMessage << "Cheb4 <n|MdagM|n> "<<norm2(tmp)<<std::endl;
|
||||
// Cheb5(hermop,noise,Mn); scale = std::pow(norm2(Mn),-0.5); noise=Mn*scale;
|
||||
// hermop.Op(noise,tmp); std::cout<<GridLogMessage << "Cheb5 <n|MdagM|n> "<<norm2(tmp)<<std::endl;
|
||||
subspace[b] = noise;
|
||||
hermop.Op(subspace[b],tmp);
|
||||
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|MdagM|n> "<<norm2(tmp)<< " norm " << norm2(noise)<<std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
virtual void CreateSubspaceMultishift(GridParallelRNG &RNG,LinearOperatorBase<FineField> &hermop,
|
||||
double Lo,double tol,int maxit)
|
||||
{
|
||||
|
||||
RealD scale;
|
||||
|
||||
FineField noise(FineGrid);
|
||||
FineField Mn(FineGrid);
|
||||
FineField tmp(FineGrid);
|
||||
|
||||
// New normalised noise
|
||||
std::cout << GridLogMessage<<" Multishift subspace : Lo "<<Lo<<std::endl;
|
||||
|
||||
// Filter
|
||||
// [ 1/6(x+Lo) - 1/2(x+2Lo) + 1/2(x+3Lo) -1/6(x+4Lo) = Lo^3 /[ (x+1Lo)(x+2Lo)(x+3Lo)(x+4Lo) ]
|
||||
//
|
||||
// 1/(x+Lo) - 1/(x+2 Lo)
|
||||
double epsilon = Lo/3;
|
||||
std::vector<RealD> alpha({1.0/6.0,-1.0/2.0,1.0/2.0,-1.0/6.0});
|
||||
std::vector<RealD> shifts({Lo,Lo+epsilon,Lo+2*epsilon,Lo+3*epsilon});
|
||||
std::vector<RealD> tols({tol,tol,tol,tol});
|
||||
std::cout << "sizes "<<alpha.size()<<" "<<shifts.size()<<" "<<tols.size()<<std::endl;
|
||||
|
||||
MultiShiftFunction msf(4,0.0,95.0);
|
||||
std::cout << "msf constructed "<<std::endl;
|
||||
msf.poles=shifts;
|
||||
msf.residues=alpha;
|
||||
msf.tolerances=tols;
|
||||
msf.norm=0.0;
|
||||
msf.order=alpha.size();
|
||||
ConjugateGradientMultiShift<FineField> MSCG(maxit,msf);
|
||||
|
||||
for(int b =0;b<nbasis;b++)
|
||||
{
|
||||
gaussian(RNG,noise);
|
||||
scale = std::pow(norm2(noise),-0.5);
|
||||
noise=noise*scale;
|
||||
|
||||
// Initial matrix element
|
||||
hermop.Op(noise,Mn);
|
||||
if(b==0) std::cout<<GridLogMessage << "noise <n|MdagM|n> "<<norm2(Mn)<<std::endl;
|
||||
|
||||
MSCG(hermop,noise,Mn);
|
||||
scale = std::pow(norm2(Mn),-0.5); Mn=Mn*scale;
|
||||
subspace[b] = Mn;
|
||||
hermop.Op(Mn,tmp);
|
||||
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|MdagM|n> "<<norm2(tmp)<<std::endl;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
virtual void RefineSubspace(LinearOperatorBase<FineField> &hermop,
|
||||
double Lo,double tol,int maxit)
|
||||
{
|
||||
FineField tmp(FineGrid);
|
||||
for(int b =0;b<nbasis;b++)
|
||||
{
|
||||
ConjugateGradient<FineField> CGsloppy(tol,maxit,false);
|
||||
ShiftedHermOpLinearOperator<FineField> ShiftedFineHermOp(hermop,Lo);
|
||||
tmp=Zero();
|
||||
CGsloppy(hermop,subspace[b],tmp);
|
||||
RealD scale = std::pow(norm2(tmp),-0.5); tmp=tmp*scale;
|
||||
subspace[b]=tmp;
|
||||
hermop.Op(subspace[b],tmp);
|
||||
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|MdagM|n> "<<norm2(tmp)<<std::endl;
|
||||
}
|
||||
}
|
||||
virtual void RefineSubspaceHDCG(LinearOperatorBase<FineField> &hermop,
|
||||
TwoLevelCGmrhs<FineField> & theHDCG,
|
||||
int nrhs)
|
||||
{
|
||||
std::vector<FineField> src_mrhs(nrhs,FineGrid);
|
||||
std::vector<FineField> res_mrhs(nrhs,FineGrid);
|
||||
FineField tmp(FineGrid);
|
||||
for(int b =0;b<nbasis;b+=nrhs)
|
||||
{
|
||||
tmp = subspace[b];
|
||||
RealD scale = std::pow(norm2(tmp),-0.5); tmp=tmp*scale;
|
||||
subspace[b] =tmp;
|
||||
hermop.Op(subspace[b],tmp);
|
||||
std::cout<<GridLogMessage << "before filt ["<<b<<"] <n|MdagM|n> "<<norm2(tmp)<<std::endl;
|
||||
|
||||
for(int r=0;r<MIN(nbasis-b,nrhs);r++){
|
||||
src_mrhs[r] = subspace[b+r];
|
||||
}
|
||||
for(int r=0;r<nrhs;r++){
|
||||
res_mrhs[r] = Zero();
|
||||
}
|
||||
theHDCG(src_mrhs,res_mrhs);
|
||||
|
||||
for(int r=0;r<MIN(nbasis-b,nrhs);r++){
|
||||
tmp = res_mrhs[r];
|
||||
RealD scale = std::pow(norm2(tmp),-0.5); tmp=tmp*scale;
|
||||
subspace[b+r]=tmp;
|
||||
}
|
||||
hermop.Op(subspace[b],tmp);
|
||||
std::cout<<GridLogMessage << "after filt ["<<b<<"] <n|MdagM|n> "<<norm2(tmp)<<std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
NAMESPACE_END(Grid);
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./Grid/algorithms/multigrid/BlockCyclic.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution
|
||||
directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Scalar of the distributed dense inversion: block-cyclic redistribution,
|
||||
// SUMMA, the Schur recursion and its LU leaf. A configure-time choice
|
||||
// (--enable-dense-inverse-precision=double|single), INDEPENDENT of the
|
||||
// coarse-space precision: the matrix elements arrive in the coarse scalar
|
||||
// and the apply slab is fp32 either way; only the factorisation's
|
||||
// arithmetic and footprint follow this type.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#ifdef GRID_DENSE_INVERSE_SINGLE
|
||||
typedef ComplexF DenseInverseScalar;
|
||||
#else
|
||||
typedef ComplexD DenseInverseScalar;
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// BlockCyclicLayout: the index arithmetic of a 2D block-cyclic distribution
|
||||
// of an N x N matrix over a Pr x Pc logical process grid with block size nb.
|
||||
//
|
||||
// This is stage 1 of the 2D distributed dense inverse. It is deliberately
|
||||
// COMMUNICATOR-FREE: every mapping is a static pure function of
|
||||
// (N, nb, Pr, Pc), so the whole layout is exhaustively unit-testable on one
|
||||
// rank with no MPI in the loop (Test_blockcyclic). A thin instance layer
|
||||
// binds a world rank to a grid coordinate and caches local extents.
|
||||
//
|
||||
// Conventions (fixed here, relied on by every later stage):
|
||||
//
|
||||
// * Global block b of a dimension with Pg processes is owned by process
|
||||
// coordinate b % Pg (ScaLAPACK csrc=0), and is that process's local
|
||||
// block b / Pg.
|
||||
// * Rank <-> grid coordinate is ROW MAJOR over the process grid:
|
||||
// rank = p*Pc + q , p = rank/Pc , q = rank%Pc .
|
||||
// The eventual ring transport must construct its neighbour tables with
|
||||
// the same convention.
|
||||
// * Local storage is COLUMN MAJOR with ld = mloc, matching BlockRows:
|
||||
// local element (i,j) lives at data[i + j*mloc].
|
||||
// * The trailing partial block (N % nb != 0) belongs to the owner of the
|
||||
// last full-size block position; only that one block is short.
|
||||
//
|
||||
// Element (gi,gj) therefore lives on grid coordinate
|
||||
// ( (gi/nb) % Pr , (gj/nb) % Pc )
|
||||
// at local coordinate
|
||||
// ( ((gi/nb)/Pr)*nb + gi%nb , ((gj/nb)/Pc)*nb + gj%nb ).
|
||||
//
|
||||
// Everything here is host-side integer arithmetic; nothing allocates.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class BlockCyclicLayout
|
||||
{
|
||||
public:
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Closest-to-square factorisation Pr*Pc == P with Pr <= Pc.
|
||||
// For fixed P the per-rank SUMMA volume N^2 (1/Pr + 1/Pc) is minimised
|
||||
// at the most square grid. P=288 -> 16 x 18.
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Nearest-to-square factorisation with Pr >= Pc. The orientation matters
|
||||
// for SUMMA: B panels travel along process columns in slots of
|
||||
// S = ceil(Pc/Pr) panels, padded; with Pr >= Pc, S == 1 and there is no
|
||||
// padding. (288 as 16x18 had S=2 -- half the B-ring bytes were zeros;
|
||||
// 18x16 moves ~30% fewer bytes for the same inverse.)
|
||||
static void ChooseProcessGrid(int P, int &Pr, int &Pc)
|
||||
{
|
||||
GRID_ASSERT(P >= 1);
|
||||
int r = 1;
|
||||
for(int f=1; (int64_t)f*f <= (int64_t)P; f++)
|
||||
if ( P % f == 0 ) r = f;
|
||||
Pc = r;
|
||||
Pr = P / r;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Number of rows (or columns) of a dimension of global extent N, block nb,
|
||||
// owned by process coordinate p of Pg. ScaLAPACK "numroc", csrc=0.
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
static int64_t NumLocal(int64_t N, int64_t nb, int p, int Pg)
|
||||
{
|
||||
GRID_ASSERT(N >= 0);
|
||||
GRID_ASSERT(nb >= 1);
|
||||
GRID_ASSERT(p >= 0);
|
||||
GRID_ASSERT(p < Pg);
|
||||
int64_t nblocks = N / nb; // full blocks
|
||||
int64_t extra = N % nb; // trailing partial block
|
||||
int64_t full = nblocks / Pg; // full blocks everyone owns
|
||||
int64_t rem = nblocks % Pg; // coords [0,rem) own one more
|
||||
int64_t n = full*nb;
|
||||
if ( p < (int)rem ) n += nb; // an extra full block
|
||||
if ( p == (int)rem ) n += extra; // the partial block, if any
|
||||
return n;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Global index -> (owner coordinate, local index) in one dimension.
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
static void GlobalToLocal(int64_t g, int64_t nb, int Pg,
|
||||
int &owner, int64_t &loc)
|
||||
{
|
||||
GRID_ASSERT(g >= 0);
|
||||
int64_t b = g / nb; // global block
|
||||
owner = (int)(b % Pg);
|
||||
loc = (b / Pg)*nb + (g % nb);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// (process coordinate, local index) -> global index in one dimension.
|
||||
// Inverse of GlobalToLocal on the owned set.
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
static int64_t LocalToGlobal(int64_t l, int64_t nb, int p, int Pg)
|
||||
{
|
||||
GRID_ASSERT(l >= 0);
|
||||
int64_t lb = l / nb; // local block
|
||||
int64_t b = lb*Pg + p; // global block
|
||||
return b*nb + (l % nb);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Instance layer: bind a rank of a Pr x Pc grid.
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
int64_t N; // global matrix dimension (square)
|
||||
int64_t nb; // block size
|
||||
int Pr, Pc; // process grid
|
||||
int me; // world rank within the grid, row major
|
||||
int prow, pcol;// my grid coordinate
|
||||
int64_t mloc,nloc; // my local extents; storage column major, ld = mloc
|
||||
|
||||
BlockCyclicLayout(int64_t N_, int64_t nb_, int Pr_, int Pc_, int me_)
|
||||
{
|
||||
N = N_;
|
||||
nb = nb_;
|
||||
Pr = Pr_;
|
||||
Pc = Pc_;
|
||||
me = me_;
|
||||
GRID_ASSERT( N >= 0 );
|
||||
GRID_ASSERT( nb >= 1 );
|
||||
GRID_ASSERT( Pr >= 1 );
|
||||
GRID_ASSERT( Pc >= 1 );
|
||||
GRID_ASSERT( me >= 0 );
|
||||
GRID_ASSERT( me < Pr*Pc );
|
||||
prow = me / Pc; // ROW MAJOR rank convention
|
||||
pcol = me % Pc;
|
||||
mloc = NumLocal(N, nb, prow, Pr);
|
||||
nloc = NumLocal(N, nb, pcol, Pc);
|
||||
}
|
||||
|
||||
// Owning rank of global element (gi,gj), row-major rank convention.
|
||||
int OwnerRank(int64_t gi, int64_t gj) const
|
||||
{
|
||||
int pr,pc; int64_t li,lj;
|
||||
GlobalToLocal(gi, nb, Pr, pr, li);
|
||||
GlobalToLocal(gj, nb, Pc, pc, lj);
|
||||
return pr*Pc + pc;
|
||||
}
|
||||
|
||||
// My local storage offset of global element (gi,gj).
|
||||
// The caller must know I own it; asserted, not assumed.
|
||||
int64_t LocalOffset(int64_t gi, int64_t gj) const
|
||||
{
|
||||
int pr,pc; int64_t li,lj;
|
||||
GlobalToLocal(gi, nb, Pr, pr, li);
|
||||
GlobalToLocal(gj, nb, Pc, pc, lj);
|
||||
GRID_ASSERT( pr == prow );
|
||||
GRID_ASSERT( pc == pcol );
|
||||
return li + lj*mloc; // column major, ld = mloc
|
||||
}
|
||||
|
||||
// Do I own global element (gi,gj)?
|
||||
int Owns(int64_t gi, int64_t gj) const
|
||||
{
|
||||
return OwnerRank(gi,gj) == me;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Block-aligned global range [g0,g1) -> my contiguous local range [l0,l1).
|
||||
//
|
||||
// For fixed owner p the local index is monotone in the global index, so a
|
||||
// coordinate's owned elements of ANY global range are contiguous in local
|
||||
// storage; and for a BLOCK-ALIGNED range the bounds are exactly
|
||||
// NumLocal(g0) and NumLocal(g1), because NumLocal(g,...) counts the owned
|
||||
// elements below g. This is what lets a windowed product view the local
|
||||
// sub-matrix of a global window as &data[l0 + c0*mloc] with the SAME ld --
|
||||
// no gather, no copy. Verified exhaustively in Test_blockcyclic T7.
|
||||
//
|
||||
// g0 must be a block multiple; g1 a block multiple or N itself.
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
static void RangeToLocal(int64_t g0, int64_t g1,
|
||||
int64_t N, int64_t nb, int p, int Pg,
|
||||
int64_t &l0, int64_t &l1)
|
||||
{
|
||||
GRID_ASSERT( 0 <= g0 );
|
||||
GRID_ASSERT( g0 <= g1 );
|
||||
GRID_ASSERT( g1 <= N );
|
||||
GRID_ASSERT( g0 % nb == 0 );
|
||||
GRID_ASSERT( (g1 % nb == 0) || (g1 == N) );
|
||||
l0 = NumLocal(g0, nb, p, Pg);
|
||||
l1 = NumLocal(g1, nb, p, Pg);
|
||||
}
|
||||
|
||||
// Instance forms, rows and columns of my own coordinate.
|
||||
void RowRange(int64_t g0, int64_t g1, int64_t &l0, int64_t &l1) const
|
||||
{ RangeToLocal(g0,g1,N,nb,prow,Pr,l0,l1); }
|
||||
void ColRange(int64_t g0, int64_t g1, int64_t &l0, int64_t &l1) const
|
||||
{ RangeToLocal(g0,g1,N,nb,pcol,Pc,l0,l1); }
|
||||
|
||||
// Size of global block b (the trailing block may be short).
|
||||
int64_t BlockSize(int64_t b) const
|
||||
{
|
||||
int64_t lo = b*nb;
|
||||
GRID_ASSERT( lo < N );
|
||||
return std::min(N, lo+nb) - lo;
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,287 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./Grid/algorithms/multigrid/BlockCyclicRedistribute.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution
|
||||
directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
#include <Grid/algorithms/multigrid/BlockCyclicSumma.h>
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// My rows of a distributed dense matrix in the 1D rank-major row layout:
|
||||
// rank r owns contiguous global rows [rowStart[r], rowStart[r+1]) of an
|
||||
// N x N matrix, stored rows x cols column major, ld = rows; element (i,j)
|
||||
// at data[i + j*ld]. This is the layout the stencil->dense import
|
||||
// produces and the apply slab consumes; the 2D inverse slots between them
|
||||
// through the redistribution below.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class BlockRows
|
||||
{
|
||||
public:
|
||||
deviceVector<DenseInverseScalar> data;
|
||||
int64_t rows;
|
||||
int64_t cols;
|
||||
int64_t ld;
|
||||
|
||||
BlockRows()
|
||||
{
|
||||
rows = 0;
|
||||
cols = 0;
|
||||
ld = 0;
|
||||
}
|
||||
void Resize(int64_t r, int64_t c)
|
||||
{
|
||||
rows = r;
|
||||
cols = c;
|
||||
ld = r;
|
||||
data.resize((uint64_t)r*c);
|
||||
}
|
||||
DenseInverseScalar *ColumnWindow(int64_t col0)
|
||||
{
|
||||
GRID_ASSERT( col0 >= 0 );
|
||||
GRID_ASSERT( col0 <= cols );
|
||||
return &data[(uint64_t)col0*ld];
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Stage 4 of the 2D distributed dense inverse: redistribution between the
|
||||
// 1D rank-major row layout (BlockRows above) and the 2D block-cyclic
|
||||
// layout.
|
||||
//
|
||||
// This is what lets the EXISTING stencil->dense import, its certificate,
|
||||
// the fp32 slab conversion and the apply path all remain byte-for-byte
|
||||
// untouched: the 2D inverse slots between them as
|
||||
//
|
||||
// RowsToCyclic -> BlockCyclicSchurInverse::Invert -> CyclicToRows
|
||||
//
|
||||
// Volume is one matrix pass each way -- N^2/P elements per rank (~1 GB at
|
||||
// production), trivial against the inversion itself.
|
||||
//
|
||||
// Transport: PURE POINT-TO-POINT, like everything else in this stack.
|
||||
// Ranks exchange in a round-robin TOURNAMENT (the circle method, on an odd
|
||||
// modulus M so it covers every pair exactly once for any P, with byes):
|
||||
// at round r, ranks x and y are partners iff x+y == r (mod M). Each
|
||||
// meeting handles both directed edges of the pair in ONE SendToRecvFrom,
|
||||
// padded to the larger of the two edge sizes -- SendToRecvFrom carries a
|
||||
// single byte count for both directions, and both endpoints compute the
|
||||
// same max from the shared descriptors, so there is no asymmetric-size
|
||||
// case and no zero-count shape. Pairs with nothing to exchange skip the
|
||||
// round, decided identically at both ends.
|
||||
//
|
||||
// Element enumeration within an edge is canonical -- ascending global
|
||||
// column outer, ascending global row inner -- and each endpoint builds its
|
||||
// OWN local offset tables from the shared descriptors, so no index data is
|
||||
// ever transmitted. The round trip is BITWISE exact (pure data movement,
|
||||
// no arithmetic): Test_schur2d_redist proves it.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class BlockCyclicRedistribute
|
||||
{
|
||||
public:
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// The directed edge (1D rank r1, 2D rank r2): global rows of r1's range
|
||||
// whose row-block coordinate is r2's prow; ALL global columns whose
|
||||
// column-block coordinate is r2's pcol. Every rank can enumerate any
|
||||
// edge from (rowStart, layout) alone.
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
static void EdgeRows(const std::vector<int64_t> &rowStart, int r1,
|
||||
const BlockCyclicLayout &L, int r2,
|
||||
std::vector<int64_t> &rows)
|
||||
{
|
||||
rows.clear();
|
||||
int p = r2 / L.Pc; // row-major rank convention
|
||||
for(int64_t i=rowStart[r1]; i<rowStart[r1+1]; i++)
|
||||
if ( (int)((i/L.nb) % L.Pr) == p ) rows.push_back(i);
|
||||
}
|
||||
static void EdgeCols(const BlockCyclicLayout &L, int r2,
|
||||
std::vector<int64_t> &cols)
|
||||
{
|
||||
cols.clear();
|
||||
int q = r2 % L.Pc;
|
||||
for(int64_t b=0; b*L.nb<L.N; b++){
|
||||
if ( (int)(b % L.Pc) != q ) continue;
|
||||
int64_t g0=b*L.nb, g1=std::min(L.N,(b+1)*L.nb);
|
||||
for(int64_t j=g0;j<g1;j++) cols.push_back(j);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// Gather/scatter one edge between a matrix (device, column major, ld)
|
||||
// and a dense edge buffer, through device offset tables.
|
||||
// buffer(a,b) = elem(rows[a], cols[b]), a fastest.
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
static void MoveEdge(int toBuffer,
|
||||
DenseInverseScalar *mat, int64_t ld,
|
||||
const std::vector<int64_t> &roff, // per-row offset in mat
|
||||
const std::vector<int64_t> &coff, // per-col offset in mat
|
||||
DenseInverseScalar *buf)
|
||||
{
|
||||
int64_t nr = roff.size();
|
||||
int64_t nc = coff.size();
|
||||
if ( !(nr && nc) ) return;
|
||||
deviceVector<int64_t> dro(nr), dco(nc);
|
||||
acceleratorCopyToDevice((void *)&roff[0], (void *)&dro[0], nr*sizeof(int64_t));
|
||||
acceleratorCopyToDevice((void *)&coff[0], (void *)&dco[0], nc*sizeof(int64_t));
|
||||
int64_t *ro = &dro[0];
|
||||
int64_t *co = &dco[0];
|
||||
if ( toBuffer ) {
|
||||
accelerator_for(idx, (uint64_t)(nr*nc), 1, {
|
||||
int64_t b = idx / nr;
|
||||
int64_t a = idx - b*nr;
|
||||
buf[a + b*nr] = mat[ ro[a] + co[b]*ld ];
|
||||
});
|
||||
} else {
|
||||
accelerator_for(idx, (uint64_t)(nr*nc), 1, {
|
||||
int64_t b = idx / nr;
|
||||
int64_t a = idx - b*nr;
|
||||
mat[ ro[a] + co[b]*ld ] = buf[a + b*nr];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// My offset tables for an edge, on whichever side I am.
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
static void Offsets1D(const std::vector<int64_t> &rows,
|
||||
const std::vector<int64_t> &cols,
|
||||
int64_t row0,
|
||||
std::vector<int64_t> &roff, std::vector<int64_t> &coff)
|
||||
{
|
||||
roff.resize(rows.size()); coff.resize(cols.size());
|
||||
for(uint64_t a=0;a<rows.size();a++) roff[a] = rows[a]-row0; // local row
|
||||
for(uint64_t b=0;b<cols.size();b++) coff[b] = cols[b]; // global col
|
||||
}
|
||||
static void Offsets2D(const BlockCyclicLayout &L,
|
||||
const std::vector<int64_t> &rows,
|
||||
const std::vector<int64_t> &cols,
|
||||
std::vector<int64_t> &roff, std::vector<int64_t> &coff)
|
||||
{
|
||||
roff.resize(rows.size()); coff.resize(cols.size());
|
||||
for(uint64_t a=0;a<rows.size();a++){
|
||||
int p; int64_t l;
|
||||
BlockCyclicLayout::GlobalToLocal(rows[a], L.nb, L.Pr, p, l);
|
||||
GRID_ASSERT( p == L.prow );
|
||||
roff[a] = l;
|
||||
}
|
||||
for(uint64_t b=0;b<cols.size();b++){
|
||||
int q; int64_t l;
|
||||
BlockCyclicLayout::GlobalToLocal(cols[b], L.nb, L.Pc, q, l);
|
||||
GRID_ASSERT( q == L.pcol );
|
||||
coff[b] = l;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// The worker. dir=+1 : 1D rows -> block cyclic ; dir=-1 : back.
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
static void Redistribute(int dir, GridBase *grid,
|
||||
const std::vector<int64_t> &rowStart,
|
||||
DenseInverseScalar *rows1d, int64_t myrows,
|
||||
BlockCyclicMatrix &A)
|
||||
{
|
||||
BlockCyclicLayout &L = A.layout;
|
||||
int P = grid->ProcessorCount();
|
||||
int me = grid->ThisRank();
|
||||
GRID_ASSERT( (int)rowStart.size() == P+1 );
|
||||
GRID_ASSERT( rowStart[P] == L.N );
|
||||
GRID_ASSERT( rowStart[me+1]-rowStart[me] == myrows );
|
||||
int64_t row0 = rowStart[me];
|
||||
int64_t ld1 = myrows ? myrows : 1;
|
||||
|
||||
std::vector<int64_t> rows, cols, roff, coff;
|
||||
deviceVector<DenseInverseScalar> sbuf(1), rbuf(1);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Self edge: purely local, via a bounce buffer (shares all the code).
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
EdgeRows(rowStart, me, L, me, rows);
|
||||
EdgeCols(L, me, cols);
|
||||
if ( rows.size() && cols.size() ){
|
||||
uint64_t ne = rows.size()*cols.size();
|
||||
if ( sbuf.size() < ne ) sbuf.resize(ne);
|
||||
std::vector<int64_t> roff2, coff2;
|
||||
Offsets1D(rows, cols, row0, roff, coff);
|
||||
Offsets2D(L, rows, cols, roff2, coff2);
|
||||
if ( dir > 0 ) {
|
||||
MoveEdge(1, rows1d, ld1, roff, coff, &sbuf[0]);
|
||||
MoveEdge(0, &A.data[0], L.mloc, roff2, coff2, &sbuf[0]);
|
||||
} else {
|
||||
MoveEdge(1, &A.data[0], L.mloc, roff2, coff2, &sbuf[0]);
|
||||
MoveEdge(0, rows1d, ld1, roff, coff, &sbuf[0]);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Tournament over all pairs: odd modulus M, partner = (r - me) mod M.
|
||||
// Every unordered pair meets exactly once; partner==me or >=P is a bye.
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
int M = (P%2) ? P : P+1;
|
||||
for(int r=0;r<M;r++){
|
||||
int partner = (int)(((int64_t)r - me + 2L*M) % M);
|
||||
if ( partner == me || partner >= P ) continue;
|
||||
|
||||
// outbound edge: my (dir>0 ? 1D rows : 2D data) -> partner
|
||||
// inbound edge: partner -> my (dir>0 ? 2D data : 1D rows)
|
||||
std::vector<int64_t> orow, ocol, irow, icol;
|
||||
if ( dir > 0 ) { EdgeRows(rowStart, me, L, partner, orow); EdgeCols(L, partner, ocol);
|
||||
EdgeRows(rowStart, partner, L, me, irow); EdgeCols(L, me, icol); }
|
||||
else { EdgeRows(rowStart, partner, L, me, orow); EdgeCols(L, me, ocol);
|
||||
EdgeRows(rowStart, me, L, partner, irow); EdgeCols(L, partner, icol); }
|
||||
|
||||
uint64_t nout = orow.size()*ocol.size();
|
||||
uint64_t nin = irow.size()*icol.size();
|
||||
if ( !(nout || nin) ) continue; // both ends compute this identically
|
||||
|
||||
uint64_t nmax = std::max(nout,nin); // symmetric padded transfer
|
||||
if ( sbuf.size() < nmax ) sbuf.resize(nmax);
|
||||
if ( rbuf.size() < nmax ) rbuf.resize(nmax);
|
||||
|
||||
if ( nout ){
|
||||
if ( dir > 0 ) { Offsets1D(orow, ocol, row0, roff, coff);
|
||||
MoveEdge(1, rows1d, ld1, roff, coff, &sbuf[0]); }
|
||||
else { Offsets2D(L, orow, ocol, roff, coff);
|
||||
MoveEdge(1, &A.data[0], L.mloc, roff, coff, &sbuf[0]); }
|
||||
}
|
||||
grid->SendToRecvFrom((void *)&sbuf[0], partner,
|
||||
(void *)&rbuf[0], partner,
|
||||
nmax*sizeof(DenseInverseScalar));
|
||||
if ( nin ){
|
||||
if ( dir > 0 ) { Offsets2D(L, irow, icol, roff, coff);
|
||||
MoveEdge(0, &A.data[0], L.mloc, roff, coff, &rbuf[0]); }
|
||||
else { Offsets1D(irow, icol, row0, roff, coff);
|
||||
MoveEdge(0, rows1d, ld1, roff, coff, &rbuf[0]); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void RowsToCyclic(GridBase *grid, const std::vector<int64_t> &rowStart,
|
||||
DenseInverseScalar *rows1d, int64_t myrows, BlockCyclicMatrix &A)
|
||||
{
|
||||
Redistribute(+1, grid, rowStart, rows1d, myrows, A);
|
||||
}
|
||||
|
||||
static void CyclicToRows(GridBase *grid, const std::vector<int64_t> &rowStart,
|
||||
BlockCyclicMatrix &A, DenseInverseScalar *rows1d, int64_t myrows)
|
||||
{
|
||||
Redistribute(-1, grid, rowStart, rows1d, myrows, A);
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,450 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./Grid/algorithms/multigrid/BlockCyclicSchurInverse.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution
|
||||
directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
#include <Grid/algorithms/blas/BatchedInverse.h>
|
||||
#include <Grid/algorithms/multigrid/BlockCyclicSumma.h>
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Stage 3 of the 2D distributed dense inverse: the recursive Schur
|
||||
// complement on a block-cyclic matrix, in place.
|
||||
//
|
||||
// The recursion splits the GLOBAL INDEX RANGE at the block boundary
|
||||
// nearest the midpoint -- not the rank range as the retired 1D
|
||||
// RecursiveSchurInverse did -- so every rank owns part of every sub-block
|
||||
// at every depth, and the ownership gating (inI/inJ, dummy operands,
|
||||
// zero-width rank ranges) a 1D scheme needs has no analogue here.
|
||||
//
|
||||
// I = [c0,m) J = [m,c1) (block-aligned, m the mid block boundary)
|
||||
// 1. recurse I : A11 -> A11inv (in place)
|
||||
// 2. Bt = A11inv . A12 (scratch, I x J)
|
||||
// 3. Ct = A21 . A11inv (scratch, J x I)
|
||||
// 4. A22 -= A21 . Bt == S (in place)
|
||||
// 5. recurse J : S -> Sinv (in place)
|
||||
// 6. Tt = Sinv . Ct (scratch, J x I)
|
||||
// 7. Ut = Bt . Sinv (scratch, I x J)
|
||||
// 8. A11 += Ut . Ct == X11 (in place)
|
||||
// 9. A12 = -Ut , A21 = -Tt (window copies)
|
||||
//
|
||||
// SCRATCH SHARING. Four full-size block-cyclic scratch matrices (Bt, Ct,
|
||||
// Tt, Ut) serve the ENTIRE tree, used through windows. This is safe at
|
||||
// every depth because of a window-disjointness invariant:
|
||||
//
|
||||
// * every temporary of a node has its row range in one half of the
|
||||
// node's window and its column range in the other (I x J or J x I);
|
||||
// * everything any DESCENDANT touches -- its A windows and its own
|
||||
// temporaries -- has BOTH ranges inside a single half (I x I during
|
||||
// step 1, J x J during step 5).
|
||||
//
|
||||
// Hence a descendant window and a live ancestor temporary always differ in
|
||||
// at least one dimension by disjoint ranges. Only Bt and Ct are live
|
||||
// across the step-5 recursion (Tt, Ut are written after it), and both are
|
||||
// covered by the invariant.
|
||||
//
|
||||
// LEAF. A leaf is a single diagonal block, and block (b,b) of a
|
||||
// block-cyclic layout lives ENTIRELY on rank (b%Pr, b%Pc). The leaf
|
||||
// inversion is therefore purely local -- pack the strided block dense,
|
||||
// GridBLASInverse, unpack -- with NO communication and no assembly.
|
||||
// Successive leaves cycle over ranks, so leaf work is naturally spread.
|
||||
//
|
||||
// COMMUNICATION. Every transfer in the whole inversion is a
|
||||
// SendToRecvFrom inside BlockCyclicSumma's rings: pure point-to-point, no
|
||||
// collectives on the critical path, deterministic summation order (so
|
||||
// repeated inversions are bitwise identical). ReportTelemetry() is the
|
||||
// one optional exception: it performs reductions, and is only ever called
|
||||
// explicitly by a caller who wants the numbers.
|
||||
//
|
||||
// NUMERICS. No pivoting: every A11 and every Schur complement met on the
|
||||
// way down must be non-singular. The growth telemetry stands in for
|
||||
// pivoting.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class BlockCyclicSchurInverse
|
||||
{
|
||||
public:
|
||||
BlockCyclicSumma SUMMA;
|
||||
GridBLASInverse INV;
|
||||
|
||||
// Telemetry: accumulated LOCALLY, no comms unless ReportTelemetry().
|
||||
double telLeafMaxInv;
|
||||
uint64_t nLeaf;
|
||||
// BIG LEAVES. Below span s blocks a sub-block lives on <= s of the Pr
|
||||
// process rows / s of the Pc columns; when s is small relative to the grid
|
||||
// the SUMMA rings run on a few ranks while the rest block in their next
|
||||
// SendToRecvFrom. Instead: gather the (s*nb)^2 sub-block to one rank,
|
||||
// invert locally (inverseLU), scatter back. Fires only while
|
||||
// span < min(Pr,Pc) -- when the whole grid participates the rings are not
|
||||
// degenerate and gathering would only concentrate memory (and at the top
|
||||
// of the tree would gather the whole matrix). Larger s concentrates more
|
||||
// memory on the root; smaller s loses ring parallelism.
|
||||
int leafSpan = 9;
|
||||
uint64_t nBigLeaf = 0; int64_t maxBigW = 0;
|
||||
double tBigGather = 0, tBigInv = 0, tBigScatter = 0;
|
||||
uint64_t nNode;
|
||||
double tLeaf;
|
||||
double tGemm; // wall in Multiply calls (comms+gemm)
|
||||
double tCopy;
|
||||
|
||||
BlockCyclicSchurInverse()
|
||||
{
|
||||
telLeafMaxInv = 0.0;
|
||||
nLeaf = nNode = 0;
|
||||
tLeaf = tGemm = tCopy = 0.0;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Window copy-scale: Dst[i0:i1, j0:j1] = alpha * Src[same window].
|
||||
// Both share one layout, so the local bands coincide; pure local kernel.
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
void WindowCopyScale(DenseInverseScalar alpha,
|
||||
BlockCyclicMatrix &Src, BlockCyclicMatrix &Dst,
|
||||
int64_t i0, int64_t i1, int64_t j0, int64_t j1)
|
||||
{
|
||||
BlockCyclicLayout &L = Dst.layout;
|
||||
GRID_ASSERT( Src.layout.N==L.N && Src.layout.nb==L.nb );
|
||||
GRID_ASSERT( Src.layout.Pr==L.Pr && Src.layout.Pc==L.Pc );
|
||||
int64_t li0,li1, lj0,lj1;
|
||||
L.RowRange(i0,i1, li0,li1);
|
||||
L.ColRange(j0,j1, lj0,lj1);
|
||||
int64_t m = li1-li0, n = lj1-lj0;
|
||||
if ( !(m && n) ) return;
|
||||
DenseInverseScalar *src = Src.LocalWindow(li0,lj0);
|
||||
DenseInverseScalar *dst = Dst.LocalWindow(li0,lj0);
|
||||
int64_t ldS = Src.layout.mloc;
|
||||
int64_t ldD = L.mloc;
|
||||
tCopy -= usecond();
|
||||
accelerator_for(idx, (uint64_t)(m*n), 1, {
|
||||
int64_t jj = idx / m;
|
||||
int64_t ii = idx - jj*m;
|
||||
dst[ii + jj*ldD] = alpha*src[ii + jj*ldS];
|
||||
});
|
||||
tCopy += usecond();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Leaf: single diagonal block (b,b), entirely on rank (b%Pr, b%Pc).
|
||||
// Local pack -> dense inverse -> unpack; every other rank does nothing
|
||||
// and needs no synchronisation: the next SUMMA's rings pair them up.
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
void Leaf(BlockCyclicMatrix &A, int64_t b)
|
||||
{
|
||||
GRID_TRACE("SchurLeaf");
|
||||
BlockCyclicLayout &L = A.layout;
|
||||
nLeaf++;
|
||||
if ( (int)(b % L.Pr) != L.prow ) return;
|
||||
if ( (int)(b % L.Pc) != L.pcol ) return;
|
||||
|
||||
tLeaf -= usecond();
|
||||
int64_t g0 = b*L.nb;
|
||||
int64_t g1 = std::min(L.N, g0+L.nb);
|
||||
int64_t w = g1-g0;
|
||||
int64_t lr0,lr1, lc0,lc1;
|
||||
L.RowRange(g0,g1, lr0,lr1);
|
||||
L.ColRange(g0,g1, lc0,lc1);
|
||||
GRID_ASSERT( lr1-lr0 == w );
|
||||
GRID_ASSERT( lc1-lc0 == w );
|
||||
|
||||
// Pack the strided block dense (inverseBatched assumes lda == w).
|
||||
deviceVector<DenseInverseScalar> dense((uint64_t)w*w);
|
||||
{
|
||||
DenseInverseScalar *src = A.LocalWindow(lr0,lc0);
|
||||
DenseInverseScalar *dst = &dense[0];
|
||||
int64_t ld = L.mloc;
|
||||
accelerator_for(idx, (uint64_t)(w*w), 1, {
|
||||
int64_t jj = idx / w;
|
||||
int64_t ii = idx - jj*w;
|
||||
dst[ii + jj*w] = src[ii + jj*ld];
|
||||
});
|
||||
}
|
||||
{
|
||||
deviceVector<DenseInverseScalar*> bp(1);
|
||||
std::vector<DenseInverseScalar*> ptr(1);
|
||||
ptr[0] = &dense[0];
|
||||
acceleratorCopyToDevice(&ptr[0], &bp[0], sizeof(DenseInverseScalar*));
|
||||
INV.inverseBatched(w, bp);
|
||||
}
|
||||
{
|
||||
DenseInverseScalar *src = &dense[0];
|
||||
DenseInverseScalar *dst = A.LocalWindow(lr0,lc0);
|
||||
int64_t ld = L.mloc;
|
||||
accelerator_for(idx, (uint64_t)(w*w), 1, {
|
||||
int64_t jj = idx / w;
|
||||
int64_t ii = idx - jj*w;
|
||||
dst[ii + jj*ld] = src[ii + jj*w];
|
||||
});
|
||||
}
|
||||
// Growth telemetry, local only.
|
||||
{
|
||||
std::vector<DenseInverseScalar> h((uint64_t)w*w);
|
||||
acceleratorCopyFromDevice(&dense[0], &h[0], h.size()*sizeof(DenseInverseScalar));
|
||||
double mx = 0.0;
|
||||
for(auto &z : h){
|
||||
double re=z.real(), im=z.imag();
|
||||
mx = std::max(mx, re*re+im*im);
|
||||
}
|
||||
telLeafMaxInv = std::max(telLeafMaxInv, std::sqrt(mx));
|
||||
}
|
||||
tLeaf += usecond();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// BIG LEAF on block range [b0,b1): gather to root = owner of block (b0,b0),
|
||||
// invert there, scatter back. Rank q's piece of the sub-block is the
|
||||
// contiguous local window RowRange(c0,c1) x ColRange(c0,c1); its local row
|
||||
// ii maps to global row (brq0 + (ii/nb)*Pr)*nb + ii%nb where brq0 is q's
|
||||
// first block row >= b0 (closed form: no tables). Transport is pairwise
|
||||
// SendToRecvFrom with root (symmetric byte count: the reverse direction
|
||||
// carries a same-size dummy -- a leaf-local cost, accepted for simplicity).
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
static int64_t FirstBlock(int64_t b0, int p, int Pg)
|
||||
{
|
||||
int64_t r = ((b0 % Pg) <= p) ? b0 - (b0 % Pg) + p : b0 - (b0 % Pg) + Pg + p;
|
||||
return r;
|
||||
}
|
||||
void BigLeaf(BlockCyclicMatrix &A, int64_t b0, int64_t b1)
|
||||
{
|
||||
GRID_TRACE("SchurBigLeaf");
|
||||
BlockCyclicLayout &L = A.layout;
|
||||
GridBase *grid = A.grid;
|
||||
const int Pr=L.Pr, Pc=L.Pc, nb=(int)L.nb;
|
||||
GRID_ASSERT( L.me == L.prow*Pc + L.pcol ); // rank convention shared with the SUMMA rings
|
||||
const int64_t c0 = b0*L.nb, c1 = std::min(L.N, b1*L.nb), W = c1-c0;
|
||||
const int root = (int)((b0%Pr)*Pc + (b0%Pc));
|
||||
const int me = L.me;
|
||||
nBigLeaf++; maxBigW = std::max(maxBigW, W);
|
||||
|
||||
// my piece
|
||||
int64_t lr0,lr1,lc0,lc1; L.RowRange(c0,c1,lr0,lr1); L.ColRange(c0,c1,lc0,lc1);
|
||||
const int64_t mq = lr1-lr0, nq = lc1-lc0;
|
||||
|
||||
deviceVector<DenseInverseScalar> dense; // root only: W x W column major
|
||||
deviceVector<DenseInverseScalar> piece, dummy; // piece: my mq x nq contiguous; dummy: reverse-direction filler
|
||||
if ( me == root ) dense.resize((uint64_t)W*W);
|
||||
|
||||
auto pack_piece = [&](DenseInverseScalar *dst, int64_t m, int64_t n, int64_t r0, int64_t cc0){
|
||||
DenseInverseScalar *src = A.LocalWindow(r0,cc0); const int64_t ld = L.mloc;
|
||||
accelerator_for(idx,(uint64_t)(m*n),1,{ int64_t jj=idx/m, ii=idx-jj*m; dst[ii+jj*m] = src[ii+jj*ld]; });
|
||||
};
|
||||
auto unpack_piece = [&](DenseInverseScalar *src, int64_t m, int64_t n, int64_t r0, int64_t cc0){
|
||||
DenseInverseScalar *dst = A.LocalWindow(r0,cc0); const int64_t ld = L.mloc;
|
||||
accelerator_for(idx,(uint64_t)(m*n),1,{ int64_t jj=idx/m, ii=idx-jj*m; dst[ii+jj*ld] = src[ii+jj*m]; });
|
||||
};
|
||||
// root: piece of rank q <-> dense, via the closed-form block map
|
||||
auto root_place = [&](DenseInverseScalar *pc, int64_t m, int64_t n, int q, int to_dense){
|
||||
const int pq=q/Pc, cq=q%Pc;
|
||||
const int64_t brq0=FirstBlock(b0,pq,Pr), bcq0=FirstBlock(b0,cq,Pc);
|
||||
DenseInverseScalar *dn = &dense[0]; const int64_t WW=W, NB=nb, PR=Pr, PC=Pc, C0=c0;
|
||||
accelerator_for(idx,(uint64_t)(m*n),1,{
|
||||
int64_t jj=idx/m, ii=idx-jj*m;
|
||||
int64_t gr = (brq0 + (ii/NB)*PR)*NB + ii%NB - C0;
|
||||
int64_t gc = (bcq0 + (jj/NB)*PC)*NB + jj%NB - C0;
|
||||
if ( to_dense ) dn[gr + gc*WW] = pc[ii+jj*m]; else pc[ii+jj*m] = dn[gr + gc*WW];
|
||||
});
|
||||
};
|
||||
auto piece_dims = [&](int q, int64_t &m, int64_t &n){
|
||||
const int pq=q/Pc, cq=q%Pc;
|
||||
m = BlockCyclicLayout::NumLocal(c1,L.nb,pq,Pr) - BlockCyclicLayout::NumLocal(c0,L.nb,pq,Pr);
|
||||
n = BlockCyclicLayout::NumLocal(c1,L.nb,cq,Pc) - BlockCyclicLayout::NumLocal(c0,L.nb,cq,Pc);
|
||||
};
|
||||
|
||||
// ---- gather ----
|
||||
tBigGather -= usecond();
|
||||
if ( mq*nq ) { piece.resize((uint64_t)mq*nq); dummy.resize((uint64_t)mq*nq); pack_piece(&piece[0],mq,nq,lr0,lc0); accelerator_barrier(); }
|
||||
if ( me == root ) {
|
||||
deviceVector<DenseInverseScalar> stage;
|
||||
for(int q=0;q<Pr*Pc;q++){
|
||||
int64_t m,n; piece_dims(q,m,n); if ( !(m*n) ) continue;
|
||||
if ( q == root ) { root_place(&piece[0],m,n,q,1); continue; }
|
||||
if ( stage.size() < (uint64_t)(m*n) ) stage.resize((uint64_t)m*n);
|
||||
deviceVector<DenseInverseScalar> junk((uint64_t)m*n);
|
||||
grid->SendToRecvFrom((void *)&junk[0], q, (void *)&stage[0], q, (uint64_t)m*n*sizeof(DenseInverseScalar));
|
||||
root_place(&stage[0],m,n,q,1);
|
||||
}
|
||||
accelerator_barrier();
|
||||
} else if ( mq*nq ) {
|
||||
grid->SendToRecvFrom((void *)&piece[0], root, (void *)&dummy[0], root, (uint64_t)mq*nq*sizeof(DenseInverseScalar));
|
||||
}
|
||||
tBigGather += usecond();
|
||||
|
||||
// ---- invert on root: blocked getrf_64 + identity getrs_64 ----
|
||||
tBigInv -= usecond();
|
||||
if ( me == root ) INV.inverseLU(W, &dense[0]);
|
||||
tBigInv += usecond();
|
||||
|
||||
// ---- scatter ----
|
||||
tBigScatter -= usecond();
|
||||
if ( me == root ) {
|
||||
deviceVector<DenseInverseScalar> stage;
|
||||
for(int q=0;q<Pr*Pc;q++){
|
||||
int64_t m,n; piece_dims(q,m,n); if ( !(m*n) ) continue;
|
||||
if ( q == root ) { root_place(&piece[0],m,n,q,0); accelerator_barrier(); continue; }
|
||||
if ( stage.size() < (uint64_t)(m*n) ) stage.resize((uint64_t)m*n);
|
||||
deviceVector<DenseInverseScalar> junk((uint64_t)m*n);
|
||||
root_place(&stage[0],m,n,q,0); accelerator_barrier();
|
||||
grid->SendToRecvFrom((void *)&stage[0], q, (void *)&junk[0], q, (uint64_t)m*n*sizeof(DenseInverseScalar));
|
||||
}
|
||||
} else if ( mq*nq ) {
|
||||
grid->SendToRecvFrom((void *)&dummy[0], root, (void *)&piece[0], root, (uint64_t)mq*nq*sizeof(DenseInverseScalar));
|
||||
}
|
||||
if ( mq*nq ) { unpack_piece(&piece[0],mq,nq,lr0,lc0); accelerator_barrier(); }
|
||||
tBigScatter += usecond();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// The recursion, on global BLOCK range [b0,b1). SPMD: every rank calls
|
||||
// with identical arguments; there is no ownership gating to get wrong.
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
void SchurNode(BlockCyclicMatrix &A,
|
||||
BlockCyclicMatrix &Bt, BlockCyclicMatrix &Ct,
|
||||
BlockCyclicMatrix &Tt, BlockCyclicMatrix &Ut,
|
||||
int64_t b0, int64_t b1)
|
||||
{
|
||||
BlockCyclicLayout &L = A.layout;
|
||||
int64_t span = b1-b0;
|
||||
GRID_ASSERT( span >= 1 );
|
||||
if ( span == 1 ) { Leaf(A, b0); return; }
|
||||
if ( span <= leafSpan && span < std::min(L.Pr,L.Pc) ) { BigLeaf(A, b0, b1); return; }
|
||||
GRID_TRACE("SchurNode");
|
||||
nNode++;
|
||||
|
||||
int64_t bm = b0 + span/2;
|
||||
int64_t c0 = b0*L.nb;
|
||||
int64_t m = bm*L.nb;
|
||||
int64_t c1 = std::min(L.N, b1*L.nb);
|
||||
DenseInverseScalar one (1.0,0.0), mone(-1.0,0.0), zero(0.0,0.0);
|
||||
|
||||
// 1. A11 -> A11inv
|
||||
SchurNode(A,Bt,Ct,Tt,Ut, b0,bm);
|
||||
|
||||
tGemm -= usecond();
|
||||
// 2. Bt[I,J] = A11inv . A12
|
||||
SUMMA.Multiply(one, A, A, zero, Bt, c0,m, m,c1, c0,m );
|
||||
// 3. Ct[J,I] = A21 . A11inv
|
||||
SUMMA.Multiply(one, A, A, zero, Ct, m,c1, c0,m, c0,m );
|
||||
// 4. A22 -= A21 . Bt (the Schur complement, in place)
|
||||
SUMMA.Multiply(mone, A, Bt, one, A, m,c1, m,c1, c0,m );
|
||||
tGemm += usecond();
|
||||
|
||||
// 5. S -> Sinv (Bt, Ct live across this call: see invariant)
|
||||
SchurNode(A,Bt,Ct,Tt,Ut, bm,b1);
|
||||
|
||||
tGemm -= usecond();
|
||||
// 6. Tt[J,I] = Sinv . Ct
|
||||
SUMMA.Multiply(one, A, Ct, zero, Tt, m,c1, c0,m, m,c1 );
|
||||
// 7. Ut[I,J] = Bt . Sinv
|
||||
SUMMA.Multiply(one, Bt, A, zero, Ut, c0,m, m,c1, m,c1 );
|
||||
// 8. A11 += Ut . Ct
|
||||
SUMMA.Multiply(one, Ut, Ct, one, A, c0,m, c0,m, m,c1 );
|
||||
tGemm += usecond();
|
||||
|
||||
// 9. Off-diagonal signs
|
||||
{ GRID_TRACE("SchurCopy");
|
||||
WindowCopyScale(mone, Ut, A, c0,m, m,c1);
|
||||
WindowCopyScale(mone, Tt, A, m,c1, c0,m);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// PUBLIC ENTRY. In-place inverse of the whole matrix. Scratch (4x the
|
||||
// matrix footprint) is allocated here and released on return.
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
void Invert(BlockCyclicMatrix &A)
|
||||
{
|
||||
BlockCyclicLayout &L = A.layout;
|
||||
GRID_ASSERT( L.N >= 1 );
|
||||
int64_t nblocks = (L.N + L.nb - 1)/L.nb;
|
||||
|
||||
BlockCyclicMatrix Bt(A.grid, L.N, L.nb, L.Pr, L.Pc);
|
||||
BlockCyclicMatrix Ct(A.grid, L.N, L.nb, L.Pr, L.Pc);
|
||||
BlockCyclicMatrix Tt(A.grid, L.N, L.nb, L.Pr, L.Pc);
|
||||
BlockCyclicMatrix Ut(A.grid, L.N, L.nb, L.Pr, L.Pc);
|
||||
|
||||
telLeafMaxInv = 0.0;
|
||||
nLeaf = nNode = 0;
|
||||
tLeaf = tGemm = tCopy = 0.0;
|
||||
|
||||
SchurNode(A, Bt,Ct,Tt,Ut, 0, nblocks);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Optional, and the ONLY place any reduction happens: call it if you
|
||||
// want the numbers, never from Invert.
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
void ReportTelemetry(GridBase *grid)
|
||||
{
|
||||
RealD mx = telLeafMaxInv;
|
||||
grid->GlobalMax(mx);
|
||||
std::cout << GridLogMessage << "BlockCyclicSchurInverse:"
|
||||
<< " nodes " << nNode << " leaves " << nLeaf
|
||||
<< " max|leafinv| " << mx
|
||||
<< " (boss secs: gemm+comms " << tGemm/1.0e6
|
||||
<< " leaf " << tLeaf/1.0e6
|
||||
<< " copy " << tCopy/1.0e6 << ")"
|
||||
<< std::endl;
|
||||
if ( nBigLeaf ) {
|
||||
RealD ti = tBigInv/1.0e6; grid->GlobalMax(ti); // inverse runs on the root of each leaf: report the max over ranks
|
||||
std::cout << GridLogMessage << "BlockCyclicSchurInverse: BIG LEAVES (span " << leafSpan
|
||||
<< ", inverseLU): " << nBigLeaf
|
||||
<< " leaves, max W " << maxBigW
|
||||
<< " boss secs: gather " << tBigGather/1.0e6 << " scatter " << tBigScatter/1.0e6
|
||||
<< " inverse (max over ranks) " << ti << std::endl;
|
||||
}
|
||||
// SUMMA breakdown: boss-rank seconds, plus the ring/gemm time spread over
|
||||
// ranks (min/max) -- skew shows as max >> min.
|
||||
RealD ring = (SUMMA.tRingA+SUMMA.tRingB)/1.0e6;
|
||||
RealD rmin = -ring, rmax = ring; grid->GlobalMax(rmin); grid->GlobalMax(rmax); rmin = -rmin; // no GlobalMin: max of negation
|
||||
RealD gmin = -SUMMA.tGemm/1.0e6, gmax = SUMMA.tGemm/1.0e6; grid->GlobalMax(gmin); grid->GlobalMax(gmax); gmin = -gmin;
|
||||
double gb = SUMMA.bytesRing/1.0e9;
|
||||
std::cout << GridLogMessage << "BlockCyclicSumma:"
|
||||
<< " multiplies " << SUMMA.nMultiply << " gemms " << SUMMA.nGemm << " ring msgs " << SUMMA.nRingMsg
|
||||
<< " | boss secs: alloc " << SUMMA.tAlloc/1.0e6
|
||||
<< " pack " << SUMMA.tPack/1.0e6
|
||||
<< " ringA " << SUMMA.tRingA/1.0e6 << " ringB " << SUMMA.tRingB/1.0e6
|
||||
<< " gemm " << SUMMA.tGemm/1.0e6
|
||||
<< " | ring min/max over ranks " << rmin << "/" << rmax
|
||||
<< " gemm min/max " << gmin << "/" << gmax
|
||||
<< " | ring bytes/rank " << gb << " GB -> " << (ring>0 ? gb/ring : 0.0) << " GB/s/rank (boss)"
|
||||
<< std::endl;
|
||||
// Ring time decomposed by message size (boss rank; every rank sends the
|
||||
// same sequence of sizes). Time is wall time inside SendToRecvFrom, so it
|
||||
// includes waiting for the partner -- a bucket whose GB/s is far below the
|
||||
// probe's for the same size is wait, not wire.
|
||||
std::cout << GridLogMessage << "BlockCyclicSumma ring histogram (boss): size-bucket msgs GB xfer-secs GB/s %time" << std::endl;
|
||||
std::streamsize oldprec = std::cout.precision();
|
||||
for(int b=0;b<SUMMA.NHIST;b++){
|
||||
if ( !SUMMA.histN[b] ) continue;
|
||||
double sec = SUMMA.histUs[b]/1.0e6, g = SUMMA.histBytes[b]/1.0e9;
|
||||
double lo = (double)(1ull<<b);
|
||||
char sz[32]; if (lo>=1048576) snprintf(sz,32,"%6.1f MB",lo/1048576.); else if (lo>=1024) snprintf(sz,32,"%6.1f KB",lo/1024.); else snprintf(sz,32,"%6.0f B ",lo);
|
||||
std::cout << GridLogMessage << " >=" << sz
|
||||
<< std::setw(8) << SUMMA.histN[b]
|
||||
<< std::setw(10) << std::setprecision(3) << g
|
||||
<< std::setw(9) << std::setprecision(3) << sec
|
||||
<< std::setw(9) << std::setprecision(3) << (sec>0 ? g/sec : 0.0)
|
||||
<< std::setw(8) << std::setprecision(3) << (ring>0 ? 100.0*sec/ring : 0.0);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout.precision(oldprec);
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,349 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./Grid/algorithms/multigrid/BlockCyclicSumma.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution
|
||||
directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
#include <Grid/algorithms/multigrid/BlockCyclic.h>
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Stage 2 of the 2D distributed dense inverse: the windowed SUMMA product
|
||||
//
|
||||
// C[i0:i1, j0:j1] <- beta C[i0:i1, j0:j1]
|
||||
// + alpha A[i0:i1, k0:k1] . B[k0:k1, j0:j1]
|
||||
//
|
||||
// on block-cyclic matrices sharing one BlockCyclicLayout. All ranges are
|
||||
// BLOCK ALIGNED (multiples of nb, or N itself at the top end): stage 3's
|
||||
// recursion splits on block boundaries, so nothing else is ever needed, and
|
||||
// alignment makes every local window a contiguous band of local storage
|
||||
// (BlockCyclic.h RangeToLocal, Test_blockcyclic T7).
|
||||
//
|
||||
// Transport is PURE POINT-TO-POINT: SendToRecvFrom on explicit world ranks
|
||||
// computed from the row-major rank convention. No collectives of any kind
|
||||
// -- no sub-communicators, no broadcast, no allgather -- by design: the
|
||||
// collective pathologies measured on this machine (MPI_Allgatherv at
|
||||
// ~0.18 MB/s with skewed counts, mpir_request.h:508 aborts, allreduce at
|
||||
// 39% of the P2P rate) motivated this implementation. SendToRecvFrom is
|
||||
// the most exercised device-buffer path in Grid and the only one never
|
||||
// implicated.
|
||||
//
|
||||
// Algorithm: round-based ring allgather SUMMA. The k range is processed in
|
||||
// rounds of Pc consecutive global blocks. Within a round
|
||||
//
|
||||
// * process column c owns at most one A panel (blocks s with s%Pc == c);
|
||||
// the Pc panels circulate around each process-ROW ring in Pc-1 steps;
|
||||
// * process row r owns up to ceil(Pc/Pr) B panels (blocks s%Pr == r);
|
||||
// they circulate around each process-COLUMN ring in Pr-1 steps;
|
||||
// * every rank then accumulates Cloc += alpha * Apanel_s . Bpanel_s
|
||||
// for each block s of the round, in ascending s: a fixed summation
|
||||
// order, so REPEATED RUNS ARE BITWISE IDENTICAL (no reduction, no
|
||||
// order ambiguity -- the property the P2P doctrine buys).
|
||||
//
|
||||
// Ring chunks are PADDED to a fixed size (full nb panels, fixed
|
||||
// panels-per-origin): SendToRecvFrom carries one byte count for both
|
||||
// directions, so symmetric transfers eliminate every variable-size edge
|
||||
// case at a worst-case ~1/Pc extra volume on ragged rounds. Padding is
|
||||
// never read: GEMMs address only the leading nb_s x width of each slot.
|
||||
//
|
||||
// Per-rank received volume: (k-extent) * (mloc_i + nloc_j) elements --
|
||||
// the N^2 (1/Pr + 1/Pc) SUMMA optimum, ~sqrt(P)/2 below the 1D scheme.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class BlockCyclicMatrix
|
||||
{
|
||||
public:
|
||||
GridBase *grid; // borrowed, never owned
|
||||
BlockCyclicLayout layout;
|
||||
deviceVector<DenseInverseScalar> data; // column major, ld = layout.mloc
|
||||
|
||||
BlockCyclicMatrix(GridBase *g, int64_t N, int64_t nb, int Pr, int Pc)
|
||||
: grid(g),
|
||||
layout(N, nb, Pr, Pc, g->ThisRank())
|
||||
{
|
||||
GRID_ASSERT( Pr*Pc == g->ProcessorCount() );
|
||||
uint64_t sz = (uint64_t)layout.mloc*layout.nloc;
|
||||
data.resize( sz ? sz : 1 );
|
||||
}
|
||||
|
||||
DenseInverseScalar *LocalWindow(int64_t li, int64_t lj)
|
||||
{
|
||||
return &data[0] + li + lj*layout.mloc;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// TEST-SCALE import/export of a replicated global matrix (host, O(N^2)
|
||||
// loops, one collective in Export). For unit tests and the stage-3
|
||||
// oracle only; production data enters through the direct block-cyclic
|
||||
// import, never through these.
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
void ImportGlobal(const std::vector<DenseInverseScalar> &G)
|
||||
{
|
||||
int64_t N = layout.N;
|
||||
GRID_ASSERT( (int64_t)G.size() == N*N );
|
||||
std::vector<DenseInverseScalar> h((uint64_t)layout.mloc*layout.nloc, DenseInverseScalar(0.0,0.0));
|
||||
for(int64_t j=0;j<N;j++){
|
||||
for(int64_t i=0;i<N;i++){
|
||||
if ( layout.Owns(i,j) ) h[layout.LocalOffset(i,j)] = G[i + j*N];
|
||||
}
|
||||
}
|
||||
if ( h.size() )
|
||||
acceleratorCopyToDevice(&h[0], &data[0], h.size()*sizeof(DenseInverseScalar));
|
||||
}
|
||||
void ExportGlobal(std::vector<DenseInverseScalar> &G)
|
||||
{
|
||||
int64_t N = layout.N;
|
||||
G.assign((uint64_t)N*N, DenseInverseScalar(0.0,0.0));
|
||||
std::vector<DenseInverseScalar> h((uint64_t)layout.mloc*layout.nloc);
|
||||
if ( h.size() )
|
||||
acceleratorCopyFromDevice(&data[0], &h[0], h.size()*sizeof(DenseInverseScalar));
|
||||
for(int64_t j=0;j<N;j++){
|
||||
for(int64_t i=0;i<N;i++){
|
||||
if ( layout.Owns(i,j) ) G[i + j*N] = h[layout.LocalOffset(i,j)];
|
||||
}
|
||||
}
|
||||
if ( N ) grid->GlobalSumVector((DenseInverseScalar *)&G[0], (int)(N*N)); // zero-fill: exact
|
||||
}
|
||||
};
|
||||
|
||||
class BlockCyclicSumma
|
||||
{
|
||||
public:
|
||||
GridBLAS BLAS;
|
||||
|
||||
// Per-rank telemetry (boss-rank seconds when printed; no reductions here).
|
||||
// Every Multiply is: buffer alloc, pack panels, ring A along the process
|
||||
// row, ring B along the process column, then the local GEMMs. The rings
|
||||
// are synchronous SendToRecvFrom, so tRing is time the GPU is idle unless
|
||||
// a future version overlaps them with the GEMMs.
|
||||
// PERSISTENT ring buffers: allocated once (grow-only) and reused by every
|
||||
// Multiply, so the device addresses handed to MPI never change. Fresh
|
||||
// per-call buffers rotated through the caching allocator's blocks, and a
|
||||
// GPU-direct RDMA registration cache keyed on address then re-registers
|
||||
// per message: measured 1.26 GB/s/rank on 13 MB ring messages (production,
|
||||
// GRID_ALLOC_NCACHE_LARGE=64) against 62 s total for the same inverse when
|
||||
// hipMalloc returned a stable address.
|
||||
deviceVector<DenseInverseScalar> Abuf;
|
||||
deviceVector<DenseInverseScalar> Bbuf;
|
||||
double tAlloc=0, tPack=0, tRingA=0, tRingB=0, tGemm=0;
|
||||
uint64_t bytesRing=0, nRingMsg=0, nMultiply=0, nGemm=0;
|
||||
// Per-message-size histogram (bucket = floor(log2 bytes)): count, bytes,
|
||||
// microseconds -- decomposes the ring time by packet size so a low average
|
||||
// GB/s can be attributed (many small latency-bound messages vs slow large
|
||||
// ones vs partner-wait). Time is wall time inside SendToRecvFrom, so a
|
||||
// bucket far below the wire rate for its size is wait, not wire.
|
||||
static const int NHIST=48;
|
||||
uint64_t histN[NHIST]={0}, histBytes[NHIST]={0}; double histUs[NHIST]={0};
|
||||
void HistAdd(uint64_t bytes, double us){ int b=0; while((bytes>>b)>1) b++; histN[b]++; histBytes[b]+=bytes; histUs[b]+=us; }
|
||||
void ResetTelemetry(void){ tAlloc=tPack=tRingA=tRingB=tGemm=0; bytesRing=nRingMsg=nMultiply=nGemm=0; for(int b=0;b<NHIST;b++){histN[b]=histBytes[b]=0; histUs[b]=0;} }
|
||||
|
||||
static int Overlap(int64_t a0,int64_t a1,int64_t b0,int64_t b1)
|
||||
{ return (a0 < b1) && (b0 < a1); }
|
||||
|
||||
void Multiply(DenseInverseScalar alpha,
|
||||
BlockCyclicMatrix &A,
|
||||
BlockCyclicMatrix &B,
|
||||
DenseInverseScalar beta,
|
||||
BlockCyclicMatrix &C,
|
||||
int64_t i0, int64_t i1,
|
||||
int64_t j0, int64_t j1,
|
||||
int64_t k0, int64_t k1)
|
||||
{
|
||||
BlockCyclicLayout &L = C.layout;
|
||||
GridBase *grid = C.grid;
|
||||
const int64_t N = L.N;
|
||||
const int64_t nb = L.nb;
|
||||
const int Pr = L.Pr, Pc = L.Pc;
|
||||
const int prow = L.prow, pcol = L.pcol;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Conformability: one layout, one communicator, aligned ranges.
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
auto same = [&](BlockCyclicLayout &X){
|
||||
GRID_ASSERT( X.N==N ); GRID_ASSERT( X.nb==nb );
|
||||
GRID_ASSERT( X.Pr==Pr ); GRID_ASSERT( X.Pc==Pc );
|
||||
GRID_ASSERT( X.me==L.me );
|
||||
};
|
||||
same(A.layout); same(B.layout);
|
||||
GRID_ASSERT( A.grid==grid ); GRID_ASSERT( B.grid==grid );
|
||||
auto aligned = [&](int64_t g0,int64_t g1){
|
||||
GRID_ASSERT( 0<=g0 ); GRID_ASSERT( g0<=g1 ); GRID_ASSERT( g1<=N );
|
||||
GRID_ASSERT( g0%nb==0 ); GRID_ASSERT( (g1%nb==0)||(g1==N) );
|
||||
};
|
||||
aligned(i0,i1); aligned(j0,j1); aligned(k0,k1);
|
||||
GRID_ASSERT( k1 > k0 ); // pure scaling not supported here
|
||||
// In-place windows are legal only if the written window is disjoint
|
||||
// from anything read (stage 3 uses this; make violation loud).
|
||||
if ( &C==&A ) GRID_ASSERT( !Overlap(j0,j1,k0,k1) );
|
||||
if ( &C==&B ) GRID_ASSERT( !Overlap(i0,i1,k0,k1) );
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// My local bands of the three windows (contiguous: T7).
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
int64_t li0,li1, lj0,lj1;
|
||||
L.RowRange(i0,i1, li0,li1);
|
||||
L.ColRange(j0,j1, lj0,lj1);
|
||||
const int64_t mloc_i = li1-li0; // my rows of the i window
|
||||
const int64_t nloc_j = lj1-lj0; // my cols of the j window
|
||||
|
||||
const int64_t kb0 = k0/nb;
|
||||
const int64_t kb1 = (k1+nb-1)/nb; // block-aligned or ==N: exact
|
||||
const int64_t S = (Pc + Pr - 1)/Pr; // max B panels per origin row
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Round buffers, padded to fixed slot sizes (see header comment).
|
||||
// A: Pc slots of mloc_i x nb (slot c = panel of the block owned by c)
|
||||
// B: Pr slots of S x (nb x nloc_j)
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
const uint64_t slotA = (uint64_t)mloc_i*nb;
|
||||
const uint64_t slotB1 = (uint64_t)nb*nloc_j; // one panel
|
||||
const uint64_t slotB = (uint64_t)S*slotB1;
|
||||
nMultiply++;
|
||||
tAlloc -= usecond();
|
||||
if ( Abuf.size() < std::max<uint64_t>(slotA*Pc,1) ) Abuf.resize( std::max<uint64_t>(slotA*Pc,1) );
|
||||
if ( Bbuf.size() < std::max<uint64_t>(slotB*Pr,1) ) Bbuf.resize( std::max<uint64_t>(slotB*Pr,1) );
|
||||
tAlloc += usecond();
|
||||
|
||||
deviceVector<DenseInverseScalar *> ap(1), bp(1), cp(1);
|
||||
std::vector<DenseInverseScalar *> ptr(1);
|
||||
|
||||
int firstblock = 1;
|
||||
for(int64_t r0=kb0; r0<kb1; r0+=Pc){ // rounds of Pc k-blocks
|
||||
int64_t r1 = std::min(kb1, r0+Pc);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Pack MY panels of this round into my origin slots.
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
tPack -= usecond();
|
||||
{ GRID_TRACE("SummaPack");
|
||||
for(int64_t s=r0; s<r1; s++){
|
||||
int64_t nb_s = L.BlockSize(s);
|
||||
if ( (int)(s%Pc) == pcol && mloc_i ){ // my A panel: block-column s
|
||||
int64_t lc0, lc1;
|
||||
L.ColRange(s*nb, std::min(N,(s+1)*nb), lc0, lc1);
|
||||
GRID_ASSERT( lc1-lc0 == nb_s );
|
||||
DenseInverseScalar *src = A.LocalWindow(li0, lc0);
|
||||
DenseInverseScalar *dst = &Abuf[0] + slotA*pcol;
|
||||
int64_t ld = A.layout.mloc;
|
||||
int64_t m = mloc_i;
|
||||
accelerator_for(idx, (uint64_t)(m*nb_s), 1, {
|
||||
int64_t jj = idx / m;
|
||||
int64_t ii = idx - jj*m;
|
||||
dst[ii + jj*m] = src[ii + jj*ld];
|
||||
});
|
||||
}
|
||||
if ( (int)(s%Pr) == prow && nloc_j ){ // my B panel: block-row s
|
||||
int64_t lr0, lr1;
|
||||
L.RowRange(s*nb, std::min(N,(s+1)*nb), lr0, lr1);
|
||||
GRID_ASSERT( lr1-lr0 == nb_s );
|
||||
int64_t idxs = (s - r0 - ((prow - r0%Pr + Pr) % Pr)) / Pr; // my panel # in round
|
||||
GRID_ASSERT( idxs >= 0 ); GRID_ASSERT( idxs < S );
|
||||
DenseInverseScalar *src = B.LocalWindow(lr0, lj0);
|
||||
DenseInverseScalar *dst = &Bbuf[0] + slotB*prow + slotB1*idxs;
|
||||
int64_t ld = B.layout.mloc;
|
||||
int64_t nn = nloc_j;
|
||||
accelerator_for(idx, (uint64_t)(nb_s*nn), 1, {
|
||||
int64_t jj = idx / nb_s;
|
||||
int64_t ii = idx - jj*nb_s;
|
||||
dst[ii + jj*nb_s] = src[ii + jj*ld];
|
||||
});
|
||||
}
|
||||
}
|
||||
accelerator_barrier();
|
||||
}
|
||||
tPack += usecond();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Ring allgather along my process ROW: Pc-1 symmetric steps. At
|
||||
// step t send the slot of origin (pcol-t+1), receive origin (pcol-t).
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
if ( Pc > 1 && slotA ){
|
||||
GRID_TRACE("SummaRingA");
|
||||
tRingA -= usecond();
|
||||
int dest = prow*Pc + (pcol+1)%Pc;
|
||||
int src = prow*Pc + (pcol-1+Pc)%Pc;
|
||||
for(int t=1;t<Pc;t++){
|
||||
int cs = (pcol - t + 1 + Pc*Pc) % Pc;
|
||||
int cr = (pcol - t + Pc*Pc) % Pc;
|
||||
double tm = usecond();
|
||||
grid->SendToRecvFrom((void *)(&Abuf[0]+slotA*cs), dest,
|
||||
(void *)(&Abuf[0]+slotA*cr), src,
|
||||
slotA*sizeof(DenseInverseScalar));
|
||||
HistAdd(slotA*sizeof(DenseInverseScalar), usecond()-tm);
|
||||
bytesRing += slotA*sizeof(DenseInverseScalar); nRingMsg++;
|
||||
}
|
||||
tRingA += usecond();
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Ring allgather along my process COLUMN: Pr-1 symmetric steps.
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
if ( Pr > 1 && slotB ){
|
||||
GRID_TRACE("SummaRingB");
|
||||
tRingB -= usecond();
|
||||
int dest = ((prow+1)%Pr)*Pc + pcol;
|
||||
int src = ((prow-1+Pr)%Pr)*Pc + pcol;
|
||||
for(int t=1;t<Pr;t++){
|
||||
int rs = (prow - t + 1 + Pr*Pr) % Pr;
|
||||
int rr = (prow - t + Pr*Pr) % Pr;
|
||||
double tm = usecond();
|
||||
grid->SendToRecvFrom((void *)(&Bbuf[0]+slotB*rs), dest,
|
||||
(void *)(&Bbuf[0]+slotB*rr), src,
|
||||
slotB*sizeof(DenseInverseScalar));
|
||||
HistAdd(slotB*sizeof(DenseInverseScalar), usecond()-tm);
|
||||
bytesRing += slotB*sizeof(DenseInverseScalar); nRingMsg++;
|
||||
}
|
||||
tRingB += usecond();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Local update, ascending s: fixed order, bitwise-reproducible.
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
tGemm -= usecond();
|
||||
{ GRID_TRACE("SummaGEMM");
|
||||
for(int64_t s=r0; s<r1; s++){
|
||||
int64_t nb_s = L.BlockSize(s);
|
||||
if ( !(mloc_i && nloc_j && nb_s) ) { firstblock = 0; continue; }
|
||||
int cA = (int)(s%Pc);
|
||||
int rB = (int)(s%Pr);
|
||||
int64_t idxs = (s - r0 - ((rB - r0%Pr + Pr) % Pr)) / Pr;
|
||||
DenseInverseScalar beta_use = firstblock ? beta : DenseInverseScalar(1.0,0.0);
|
||||
firstblock = 0;
|
||||
|
||||
ptr[0] = &Abuf[0] + slotA*cA;
|
||||
acceleratorCopyToDevice(&ptr[0], &ap[0], sizeof(DenseInverseScalar *));
|
||||
ptr[0] = &Bbuf[0] + slotB*rB + slotB1*idxs;
|
||||
acceleratorCopyToDevice(&ptr[0], &bp[0], sizeof(DenseInverseScalar *));
|
||||
ptr[0] = C.LocalWindow(li0, lj0);
|
||||
acceleratorCopyToDevice(&ptr[0], &cp[0], sizeof(DenseInverseScalar *));
|
||||
|
||||
BLAS.gemmBatched(GridBLAS_OP_N, GridBLAS_OP_N,
|
||||
(int)mloc_i, (int)nloc_j, (int)nb_s,
|
||||
alpha, ap, (int)mloc_i,
|
||||
bp, (int)nb_s,
|
||||
beta_use, cp, (int)C.layout.mloc);
|
||||
BLAS.synchronise();
|
||||
nGemm++;
|
||||
}
|
||||
}
|
||||
tGemm += usecond();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -56,243 +56,6 @@ inline void blockMaskedInnerProduct(Lattice<CComplex> &CoarseInner,
|
||||
blockSum(CoarseInner,fine_inner_msk);
|
||||
}
|
||||
|
||||
|
||||
class Geometry {
|
||||
public:
|
||||
int npoint;
|
||||
int base;
|
||||
std::vector<int> directions ;
|
||||
std::vector<int> displacements;
|
||||
std::vector<int> points_dagger;
|
||||
|
||||
Geometry(int _d) {
|
||||
|
||||
base = (_d==5) ? 1:0;
|
||||
|
||||
// make coarse grid stencil for 4d , not 5d
|
||||
if ( _d==5 ) _d=4;
|
||||
|
||||
npoint = 2*_d+1;
|
||||
directions.resize(npoint);
|
||||
displacements.resize(npoint);
|
||||
points_dagger.resize(npoint);
|
||||
for(int d=0;d<_d;d++){
|
||||
directions[d ] = d+base;
|
||||
directions[d+_d] = d+base;
|
||||
displacements[d ] = +1;
|
||||
displacements[d+_d]= -1;
|
||||
points_dagger[d ] = d+_d;
|
||||
points_dagger[d+_d] = d;
|
||||
}
|
||||
directions [2*_d]=0;
|
||||
displacements[2*_d]=0;
|
||||
points_dagger[2*_d]=2*_d;
|
||||
}
|
||||
|
||||
int point(int dir, int disp) {
|
||||
assert(disp == -1 || disp == 0 || disp == 1);
|
||||
assert(base+0 <= dir && dir < base+4);
|
||||
|
||||
// directions faster index = new indexing
|
||||
// 4d (base = 0):
|
||||
// point 0 1 2 3 4 5 6 7 8
|
||||
// dir 0 1 2 3 0 1 2 3 0
|
||||
// disp +1 +1 +1 +1 -1 -1 -1 -1 0
|
||||
// 5d (base = 1):
|
||||
// point 0 1 2 3 4 5 6 7 8
|
||||
// dir 1 2 3 4 1 2 3 4 0
|
||||
// disp +1 +1 +1 +1 -1 -1 -1 -1 0
|
||||
|
||||
// displacements faster index = old indexing
|
||||
// 4d (base = 0):
|
||||
// point 0 1 2 3 4 5 6 7 8
|
||||
// dir 0 0 1 1 2 2 3 3 0
|
||||
// disp +1 -1 +1 -1 +1 -1 +1 -1 0
|
||||
// 5d (base = 1):
|
||||
// point 0 1 2 3 4 5 6 7 8
|
||||
// dir 1 1 2 2 3 3 4 4 0
|
||||
// disp +1 -1 +1 -1 +1 -1 +1 -1 0
|
||||
|
||||
if(dir == 0 and disp == 0)
|
||||
return 8;
|
||||
else // New indexing
|
||||
return (1 - disp) / 2 * 4 + dir - base;
|
||||
// else // Old indexing
|
||||
// return (4 * (dir - base) + 1 - disp) / 2;
|
||||
}
|
||||
};
|
||||
|
||||
template<class Fobj,class CComplex,int nbasis>
|
||||
class Aggregation {
|
||||
public:
|
||||
typedef iVector<CComplex,nbasis > siteVector;
|
||||
typedef Lattice<siteVector> CoarseVector;
|
||||
typedef Lattice<iMatrix<CComplex,nbasis > > CoarseMatrix;
|
||||
|
||||
typedef Lattice< CComplex > CoarseScalar; // used for inner products on fine field
|
||||
typedef Lattice<Fobj > FineField;
|
||||
|
||||
GridBase *CoarseGrid;
|
||||
GridBase *FineGrid;
|
||||
std::vector<Lattice<Fobj> > subspace;
|
||||
int checkerboard;
|
||||
int Checkerboard(void){return checkerboard;}
|
||||
Aggregation(GridBase *_CoarseGrid,GridBase *_FineGrid,int _checkerboard) :
|
||||
CoarseGrid(_CoarseGrid),
|
||||
FineGrid(_FineGrid),
|
||||
subspace(nbasis,_FineGrid),
|
||||
checkerboard(_checkerboard)
|
||||
{
|
||||
};
|
||||
|
||||
void Orthogonalise(void){
|
||||
CoarseScalar InnerProd(CoarseGrid);
|
||||
std::cout << GridLogMessage <<" Block Gramm-Schmidt pass 1"<<std::endl;
|
||||
blockOrthogonalise(InnerProd,subspace);
|
||||
}
|
||||
void ProjectToSubspace(CoarseVector &CoarseVec,const FineField &FineVec){
|
||||
blockProject(CoarseVec,FineVec,subspace);
|
||||
}
|
||||
void PromoteFromSubspace(const CoarseVector &CoarseVec,FineField &FineVec){
|
||||
FineVec.Checkerboard() = subspace[0].Checkerboard();
|
||||
blockPromote(CoarseVec,FineVec,subspace);
|
||||
}
|
||||
|
||||
virtual void CreateSubspace(GridParallelRNG &RNG,LinearOperatorBase<FineField> &hermop,int nn=nbasis) {
|
||||
|
||||
RealD scale;
|
||||
|
||||
ConjugateGradient<FineField> CG(1.0e-2,100,false);
|
||||
FineField noise(FineGrid);
|
||||
FineField Mn(FineGrid);
|
||||
|
||||
for(int b=0;b<nn;b++){
|
||||
|
||||
subspace[b] = Zero();
|
||||
gaussian(RNG,noise);
|
||||
scale = std::pow(norm2(noise),-0.5);
|
||||
noise=noise*scale;
|
||||
|
||||
hermop.Op(noise,Mn); std::cout<<GridLogMessage << "noise ["<<b<<"] <n|MdagM|n> "<<norm2(Mn)<<std::endl;
|
||||
|
||||
for(int i=0;i<1;i++){
|
||||
|
||||
CG(hermop,noise,subspace[b]);
|
||||
|
||||
noise = subspace[b];
|
||||
scale = std::pow(norm2(noise),-0.5);
|
||||
noise=noise*scale;
|
||||
|
||||
}
|
||||
|
||||
hermop.Op(noise,Mn); std::cout<<GridLogMessage << "filtered["<<b<<"] <f|MdagM|f> "<<norm2(Mn)<<std::endl;
|
||||
subspace[b] = noise;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// World of possibilities here. But have tried quite a lot of experiments (250+ jobs run on Summit)
|
||||
// and this is the best I found
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
virtual void CreateSubspaceChebyshev(GridParallelRNG &RNG,LinearOperatorBase<FineField> &hermop,
|
||||
int nn,
|
||||
double hi,
|
||||
double lo,
|
||||
int orderfilter,
|
||||
int ordermin,
|
||||
int orderstep,
|
||||
double filterlo
|
||||
) {
|
||||
|
||||
RealD scale;
|
||||
|
||||
FineField noise(FineGrid);
|
||||
FineField Mn(FineGrid);
|
||||
FineField tmp(FineGrid);
|
||||
|
||||
// New normalised noise
|
||||
gaussian(RNG,noise);
|
||||
scale = std::pow(norm2(noise),-0.5);
|
||||
noise=noise*scale;
|
||||
|
||||
// Initial matrix element
|
||||
hermop.Op(noise,Mn); std::cout<<GridLogMessage << "noise <n|MdagM|n> "<<norm2(Mn)<<std::endl;
|
||||
|
||||
int b =0;
|
||||
{
|
||||
// Filter
|
||||
Chebyshev<FineField> Cheb(lo,hi,orderfilter);
|
||||
Cheb(hermop,noise,Mn);
|
||||
// normalise
|
||||
scale = std::pow(norm2(Mn),-0.5); Mn=Mn*scale;
|
||||
subspace[b] = Mn;
|
||||
hermop.Op(Mn,tmp);
|
||||
std::cout<<GridLogMessage << "filt ["<<b<<"] <n|MdagM|n> "<<norm2(tmp)<<std::endl;
|
||||
b++;
|
||||
}
|
||||
|
||||
// Generate a full sequence of Chebyshevs
|
||||
{
|
||||
lo=filterlo;
|
||||
noise=Mn;
|
||||
|
||||
FineField T0(FineGrid); T0 = noise;
|
||||
FineField T1(FineGrid);
|
||||
FineField T2(FineGrid);
|
||||
FineField y(FineGrid);
|
||||
|
||||
FineField *Tnm = &T0;
|
||||
FineField *Tn = &T1;
|
||||
FineField *Tnp = &T2;
|
||||
|
||||
// Tn=T1 = (xscale M + mscale)in
|
||||
RealD xscale = 2.0/(hi-lo);
|
||||
RealD mscale = -(hi+lo)/(hi-lo);
|
||||
hermop.HermOp(T0,y);
|
||||
T1=y*xscale+noise*mscale;
|
||||
|
||||
for(int n=2;n<=ordermin+orderstep*(nn-2);n++){
|
||||
|
||||
hermop.HermOp(*Tn,y);
|
||||
|
||||
autoView( y_v , y, AcceleratorWrite);
|
||||
autoView( Tn_v , (*Tn), AcceleratorWrite);
|
||||
autoView( Tnp_v , (*Tnp), AcceleratorWrite);
|
||||
autoView( Tnm_v , (*Tnm), AcceleratorWrite);
|
||||
const int Nsimd = CComplex::Nsimd();
|
||||
accelerator_for(ss, FineGrid->oSites(), Nsimd, {
|
||||
coalescedWrite(y_v[ss],xscale*y_v(ss)+mscale*Tn_v(ss));
|
||||
coalescedWrite(Tnp_v[ss],2.0*y_v(ss)-Tnm_v(ss));
|
||||
});
|
||||
|
||||
// Possible more fine grained control is needed than a linear sweep,
|
||||
// but huge productivity gain if this is simple algorithm and not a tunable
|
||||
int m =1;
|
||||
if ( n>=ordermin ) m=n-ordermin;
|
||||
if ( (m%orderstep)==0 ) {
|
||||
Mn=*Tnp;
|
||||
scale = std::pow(norm2(Mn),-0.5); Mn=Mn*scale;
|
||||
subspace[b] = Mn;
|
||||
hermop.Op(Mn,tmp);
|
||||
std::cout<<GridLogMessage << n<<" filt ["<<b<<"] <n|MdagM|n> "<<norm2(tmp)<<std::endl;
|
||||
b++;
|
||||
}
|
||||
|
||||
// Cycle pointers to avoid copies
|
||||
FineField *swizzle = Tnm;
|
||||
Tnm =Tn;
|
||||
Tn =Tnp;
|
||||
Tnp =swizzle;
|
||||
|
||||
}
|
||||
}
|
||||
assert(b==nn);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Fine Object == (per site) type of fine field
|
||||
// nbasis == number of deflation vectors
|
||||
template<class Fobj,class CComplex,int nbasis>
|
||||
@@ -336,7 +99,7 @@ public:
|
||||
CoarseMatrix AselfInvEven;
|
||||
CoarseMatrix AselfInvOdd;
|
||||
|
||||
Vector<RealD> dag_factor;
|
||||
deviceVector<RealD> dag_factor;
|
||||
|
||||
///////////////////////
|
||||
// Interface
|
||||
@@ -361,9 +124,13 @@ public:
|
||||
int npoint = geom.npoint;
|
||||
typedef LatticeView<Cobj> Aview;
|
||||
|
||||
Vector<Aview> AcceleratorViewContainer;
|
||||
deviceVector<Aview> AcceleratorViewContainer(geom.npoint);
|
||||
hostVector<Aview> hAcceleratorViewContainer(geom.npoint);
|
||||
|
||||
for(int p=0;p<geom.npoint;p++) AcceleratorViewContainer.push_back(A[p].View(AcceleratorRead));
|
||||
for(int p=0;p<geom.npoint;p++) {
|
||||
hAcceleratorViewContainer[p] = A[p].View(AcceleratorRead);
|
||||
acceleratorPut(AcceleratorViewContainer[p],hAcceleratorViewContainer[p]);
|
||||
}
|
||||
Aview *Aview_p = & AcceleratorViewContainer[0];
|
||||
|
||||
const int Nsimd = CComplex::Nsimd();
|
||||
@@ -398,7 +165,7 @@ public:
|
||||
coalescedWrite(out_v[ss](b),res);
|
||||
});
|
||||
|
||||
for(int p=0;p<geom.npoint;p++) AcceleratorViewContainer[p].ViewClose();
|
||||
for(int p=0;p<geom.npoint;p++) hAcceleratorViewContainer[p].ViewClose();
|
||||
};
|
||||
|
||||
void Mdag (const CoarseVector &in, CoarseVector &out)
|
||||
@@ -427,9 +194,14 @@ public:
|
||||
int npoint = geom.npoint;
|
||||
typedef LatticeView<Cobj> Aview;
|
||||
|
||||
Vector<Aview> AcceleratorViewContainer;
|
||||
|
||||
for(int p=0;p<geom.npoint;p++) AcceleratorViewContainer.push_back(A[p].View(AcceleratorRead));
|
||||
deviceVector<Aview> AcceleratorViewContainer(geom.npoint);
|
||||
hostVector<Aview> hAcceleratorViewContainer(geom.npoint);
|
||||
|
||||
for(int p=0;p<geom.npoint;p++) {
|
||||
hAcceleratorViewContainer[p] = A[p].View(AcceleratorRead);
|
||||
acceleratorPut(AcceleratorViewContainer[p],hAcceleratorViewContainer[p]);
|
||||
}
|
||||
Aview *Aview_p = & AcceleratorViewContainer[0];
|
||||
|
||||
const int Nsimd = CComplex::Nsimd();
|
||||
@@ -438,10 +210,10 @@ public:
|
||||
|
||||
int osites=Grid()->oSites();
|
||||
|
||||
Vector<int> points(geom.npoint, 0);
|
||||
for(int p=0; p<geom.npoint; p++)
|
||||
points[p] = geom.points_dagger[p];
|
||||
|
||||
deviceVector<int> points(geom.npoint);
|
||||
for(int p=0; p<geom.npoint; p++) {
|
||||
acceleratorPut(points[p],geom.points_dagger[p]);
|
||||
}
|
||||
auto points_p = &points[0];
|
||||
|
||||
RealD* dag_factor_p = &dag_factor[0];
|
||||
@@ -473,7 +245,7 @@ public:
|
||||
coalescedWrite(out_v[ss](b),res);
|
||||
});
|
||||
|
||||
for(int p=0;p<geom.npoint;p++) AcceleratorViewContainer[p].ViewClose();
|
||||
for(int p=0;p<geom.npoint;p++) hAcceleratorViewContainer[p].ViewClose();
|
||||
}
|
||||
|
||||
void MdirComms(const CoarseVector &in)
|
||||
@@ -488,8 +260,14 @@ public:
|
||||
out.Checkerboard() = in.Checkerboard();
|
||||
|
||||
typedef LatticeView<Cobj> Aview;
|
||||
Vector<Aview> AcceleratorViewContainer;
|
||||
for(int p=0;p<geom.npoint;p++) AcceleratorViewContainer.push_back(A[p].View(AcceleratorRead));
|
||||
|
||||
deviceVector<Aview> AcceleratorViewContainer(geom.npoint);
|
||||
hostVector<Aview> hAcceleratorViewContainer(geom.npoint);
|
||||
|
||||
for(int p=0;p<geom.npoint;p++) {
|
||||
hAcceleratorViewContainer[p] = A[p].View(AcceleratorRead);
|
||||
acceleratorPut(AcceleratorViewContainer[p],hAcceleratorViewContainer[p]);
|
||||
}
|
||||
Aview *Aview_p = & AcceleratorViewContainer[0];
|
||||
|
||||
autoView( out_v , out, AcceleratorWrite);
|
||||
@@ -522,7 +300,7 @@ public:
|
||||
}
|
||||
coalescedWrite(out_v[ss](b),res);
|
||||
});
|
||||
for(int p=0;p<geom.npoint;p++) AcceleratorViewContainer[p].ViewClose();
|
||||
for(int p=0;p<geom.npoint;p++) hAcceleratorViewContainer[p].ViewClose();
|
||||
}
|
||||
void MdirAll(const CoarseVector &in,std::vector<CoarseVector> &out)
|
||||
{
|
||||
@@ -531,7 +309,7 @@ public:
|
||||
if ((out.size()!=ndir)&&(out.size()!=ndir+1)) {
|
||||
std::cout <<"MdirAll out size "<< out.size()<<std::endl;
|
||||
std::cout <<"MdirAll ndir "<< ndir<<std::endl;
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
for(int p=0;p<ndir;p++){
|
||||
MdirCalc(in,out[p],p);
|
||||
@@ -595,7 +373,7 @@ public:
|
||||
conformable(in.Grid(), _cbgrid); // verifies half grid
|
||||
conformable(in.Grid(), out.Grid()); // drops the cb check
|
||||
|
||||
assert(in.Checkerboard() == Even);
|
||||
GRID_ASSERT(in.Checkerboard() == Even);
|
||||
out.Checkerboard() = Odd;
|
||||
|
||||
DhopInternal(StencilEven, Aodd, in, out, dag);
|
||||
@@ -605,7 +383,7 @@ public:
|
||||
conformable(in.Grid(), _cbgrid); // verifies half grid
|
||||
conformable(in.Grid(), out.Grid()); // drops the cb check
|
||||
|
||||
assert(in.Checkerboard() == Odd);
|
||||
GRID_ASSERT(in.Checkerboard() == Odd);
|
||||
out.Checkerboard() = Even;
|
||||
|
||||
DhopInternal(StencilOdd, Aeven, in, out, dag);
|
||||
@@ -613,7 +391,7 @@ public:
|
||||
|
||||
void MooeeInternal(const CoarseVector &in, CoarseVector &out, int dag, int inv) {
|
||||
out.Checkerboard() = in.Checkerboard();
|
||||
assert(in.Checkerboard() == Odd || in.Checkerboard() == Even);
|
||||
GRID_ASSERT(in.Checkerboard() == Odd || in.Checkerboard() == Even);
|
||||
|
||||
CoarseMatrix *Aself = nullptr;
|
||||
if(in.Grid()->_isCheckerBoarded) {
|
||||
@@ -628,7 +406,7 @@ public:
|
||||
Aself = (inv) ? &AselfInv : &A[geom.npoint-1];
|
||||
DselfInternal(Stencil, *Aself, in, out, dag);
|
||||
}
|
||||
assert(Aself != nullptr);
|
||||
GRID_ASSERT(Aself != nullptr);
|
||||
}
|
||||
|
||||
void DselfInternal(CartesianStencil<siteVector,siteVector,DefaultImplParams> &st, CoarseMatrix &a,
|
||||
@@ -706,14 +484,20 @@ public:
|
||||
|
||||
// determine in what order we need the points
|
||||
int npoint = geom.npoint-1;
|
||||
Vector<int> points(npoint, 0);
|
||||
for(int p=0; p<npoint; p++)
|
||||
points[p] = (dag && !hermitian) ? geom.points_dagger[p] : p;
|
||||
|
||||
deviceVector<int> points(npoint);
|
||||
for(int p=0; p<npoint; p++) {
|
||||
int val = (dag && !hermitian) ? geom.points_dagger[p] : p;
|
||||
acceleratorPut(points[p], val);
|
||||
}
|
||||
auto points_p = &points[0];
|
||||
|
||||
Vector<Aview> AcceleratorViewContainer;
|
||||
for(int p=0;p<npoint;p++) AcceleratorViewContainer.push_back(a[p].View(AcceleratorRead));
|
||||
deviceVector<Aview> AcceleratorViewContainer(geom.npoint);
|
||||
hostVector<Aview> hAcceleratorViewContainer(geom.npoint);
|
||||
|
||||
for(int p=0;p<geom.npoint;p++) {
|
||||
hAcceleratorViewContainer[p] = a[p].View(AcceleratorRead);
|
||||
acceleratorPut(AcceleratorViewContainer[p],hAcceleratorViewContainer[p]);
|
||||
}
|
||||
Aview *Aview_p = & AcceleratorViewContainer[0];
|
||||
|
||||
const int Nsimd = CComplex::Nsimd();
|
||||
@@ -776,7 +560,7 @@ public:
|
||||
});
|
||||
}
|
||||
|
||||
for(int p=0;p<npoint;p++) AcceleratorViewContainer[p].ViewClose();
|
||||
for(int p=0;p<npoint;p++) hAcceleratorViewContainer[p].ViewClose();
|
||||
}
|
||||
|
||||
CoarsenedMatrix(GridCartesian &CoarseGrid, int hermitian_=0) :
|
||||
@@ -827,11 +611,13 @@ public:
|
||||
}
|
||||
|
||||
// GPU readable prefactor
|
||||
std::vector<RealD> h_dag_factor(nbasis*nbasis);
|
||||
thread_for(i, nbasis*nbasis, {
|
||||
int j = i/nbasis;
|
||||
int k = i%nbasis;
|
||||
dag_factor[i] = dag_factor_eigen(j, k);
|
||||
h_dag_factor[i] = dag_factor_eigen(j, k);
|
||||
});
|
||||
acceleratorCopyToDevice(&h_dag_factor[0],&dag_factor[0],dag_factor.size()*sizeof(RealD));
|
||||
}
|
||||
|
||||
void CoarsenOperator(GridBase *FineGrid,LinearOperatorBase<Lattice<Fobj> > &linop,
|
||||
@@ -911,7 +697,7 @@ public:
|
||||
evenmask = where(mod(bcb,2)==(Integer)0,one,zero);
|
||||
oddmask = one-evenmask;
|
||||
|
||||
assert(self_stencil!=-1);
|
||||
GRID_ASSERT(self_stencil!=-1);
|
||||
|
||||
for(int i=0;i<nbasis;i++){
|
||||
|
||||
@@ -0,0 +1,912 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./lib/algorithms/multigrid/DenseCoarseMatrix.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
#include <Grid/algorithms/blas/BatchedBlas.h>
|
||||
#include <Grid/algorithms/blas/BatchedInverse.h>
|
||||
#include <Grid/algorithms/multigrid/BlockCyclicSchurInverse.h>
|
||||
#include <Grid/algorithms/multigrid/BlockCyclicRedistribute.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// DenseCoarseMatrix: a coarsened operator treated as a DENSE matrix -- explicit,
|
||||
// row-distributed A^{-1} of a coarsened operator.
|
||||
//
|
||||
// - Stencil -> dense DIRECT IMPORT. The coarse operator IS the dense matrix
|
||||
// unrolled: Dense[(s,a),(s+shift_p,b)] += A[p][s]_{a,b}. Rows of my sites are
|
||||
// assembled from purely LOCAL _A[p] data: no operator applies, no comms.
|
||||
// ACCUMULATE (+=) because on short axes distinct shifts wrap to the same
|
||||
// neighbour. An IMPORT CERTIFICATE compares the dense apply against Op.M on a
|
||||
// NON-CONSTANT vector (a constant one cannot see a shift-sign error).
|
||||
//
|
||||
// - Inversion is END-TO-END fp64 through the 2D block-cyclic recursive Schur
|
||||
// complement (BlockCyclicSchurInverse): fp64 rank-major import ->
|
||||
// RowsToCyclic -> in-place recursion (pure point-to-point SUMMA rings and
|
||||
// local leaves; bitwise reproducible) -> CyclicToRows -> ONE terminal
|
||||
// rounding into the fp32 apply slab. Distributed at every N and P.
|
||||
//
|
||||
// - Split-K apply through GridBLAS.gemmBatched with EXPLICIT leading dimensions
|
||||
// (arXiv:2409.03904 fig 11): the tiny-output/huge-K GEMM Y = slab^T X becomes
|
||||
// SPLITK chunk-GEMMs by pointer offset into the resident slab (lda = N),
|
||||
// partials reduced in one accelerator_for. The source vector is assembled by
|
||||
// a cartesian ring ALLGATHER of device buffers (pure P2P; ~8x fewer bytes
|
||||
// than a padded allreduce, and no collective size cliffs). Platform-agnostic:
|
||||
// deviceVector + GridBLAS run the SAME code on HIP/CUDA/SYCL and CPU(Eigen).
|
||||
//
|
||||
// VERIFY ||A Ainv x - x||/||x|| certifies the DEVICE slab + split-K path at the
|
||||
// end of Import, since the single-RHS apply routes through the same core.
|
||||
//
|
||||
// Tensor-depth agnostic: site scalar objects treated as contiguous coarse
|
||||
// scalars (ComplexF or ComplexD, following the coefficient precision)
|
||||
// (iScalar wrappers add no data), so any MG level's coarse operator imports.
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Depends on a coarse operator only to extract its matrix elements; thereafter
|
||||
// it is given a coarse vector and applies the inverse. Import() is a template
|
||||
// member so any of the coarse classes will do, and the type of a
|
||||
// DenseCoarseMatrix does not record which one built it.
|
||||
//
|
||||
template<class CComplex,int nbasis>
|
||||
class DenseCoarseMatrix : public LinearFunction<Lattice<iVector<CComplex,nbasis> > > {
|
||||
public:
|
||||
typedef iVector<CComplex,nbasis > siteVector;
|
||||
typedef Lattice<siteVector> CoarseVector;
|
||||
typedef Lattice<iMatrix<CComplex,nbasis > > CoarseMatrix;
|
||||
typedef CoarseVector Field;
|
||||
using LinearFunction<Field>::operator();
|
||||
typedef typename Field::vector_object vobj;
|
||||
typedef typename vobj::scalar_object sobj;
|
||||
typedef typename CoarseMatrix::vector_object Mvobj;
|
||||
typedef typename Mvobj::scalar_object Msobj;
|
||||
// Scalar of the coarse site objects (ComplexF or ComplexD): the apply slab
|
||||
// is always fp32 and the inversion always fp64, but the SOURCE of both --
|
||||
// the coarse operator's matrix elements -- carries this precision.
|
||||
typedef typename GridTypeMapper<CComplex>::scalar_type CoarseScalar;
|
||||
|
||||
GridBase *grid;
|
||||
int nd;
|
||||
int64_t N; // dense rank = gSites * nbasis
|
||||
int lsites; // my local sites
|
||||
int64_t nrows; // my rows = lsites * nbasis
|
||||
std::vector<Coordinate> myLcoor; // local coordinate of my site ss
|
||||
std::vector<int64_t> myGsite; // global lex site index of my site ss
|
||||
std::vector<ComplexF> slab; // nrows x N row-major: A during setup, rows of A^{-1} after
|
||||
|
||||
static const int MRHS_MAX = 32;
|
||||
static const int SPLITK = 32; // requested split-K chunk count, snapped DOWN to a divisor of N
|
||||
|
||||
// Apply machinery: resident slab + persistent buffers + AOT split-K pointers.
|
||||
GridBLAS BLAS;
|
||||
deviceVector<ComplexF> dSlab;
|
||||
deviceVector<ComplexF> dX; // N x MRHS_MAX
|
||||
deviceVector<ComplexF> dY; // nrows x MRHS_MAX
|
||||
deviceVector<ComplexF> dG; // N x MRHS_MAX lex-major staging for the allgather
|
||||
deviceVector<int> dLex2Rank;// lex index of a process coordinate -> its rank (allgather block order -> row-block order)
|
||||
deviceVector<int64_t> dRm2G; // rank-major index (rank*nrows + ss*nbasis + b) -> global column (gsite*nbasis + b) of x / the slab
|
||||
int myLex;
|
||||
deviceVector<ComplexF> dPartial; // NK x (nrows x MRHS_MAX)
|
||||
deviceVector<ComplexF*> aptrs; // slab K-chunk pointers (lda = N)
|
||||
deviceVector<ComplexF*> xptrs; // X K-chunk pointers (ldb = N)
|
||||
deviceVector<ComplexF*> cptrs; // partial buffers (ldc = nrows)
|
||||
std::vector<ComplexF> hX;
|
||||
std::vector<ComplexF> hY;
|
||||
int NK; // split-K chunk count (divides N)
|
||||
|
||||
// Resident device memory: the apply slab and its staging. deviceVector is
|
||||
// not evictable, so this counts against the hard budget, not the cache.
|
||||
uint64_t DeviceBytes(void)
|
||||
{
|
||||
return (uint64_t)(dSlab.capacity()+dX.capacity()+dY.capacity()+dG.capacity()+dPartial.capacity())*sizeof(ComplexF)
|
||||
+ (uint64_t)dLex2Rank.capacity()*sizeof(int)
|
||||
+ (uint64_t)dRm2G.capacity()*sizeof(int64_t);
|
||||
}
|
||||
|
||||
DenseCoarseMatrix(GridBase *g)
|
||||
: grid(g)
|
||||
{
|
||||
GRID_ASSERT( sizeof(sobj) == nbasis*sizeof(CoarseScalar) );
|
||||
GRID_ASSERT( sizeof(Msobj) == nbasis*nbasis*sizeof(CoarseScalar) );
|
||||
nd = grid->_ndimension;
|
||||
N = grid->gSites() * nbasis;
|
||||
lsites = grid->lSites();
|
||||
nrows = (int64_t)lsites * nbasis;
|
||||
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: N = " << N
|
||||
<< " (" << grid->gSites() << " sites x " << nbasis << ")"
|
||||
<< " rows/rank = " << nrows
|
||||
<< " slab = " << (double)nrows*N*sizeof(ComplexF)/1024./1024. << " MB/rank"
|
||||
<< std::endl;
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Enumerate my sites: local coords and global lexicographic indices
|
||||
////////////////////////////////////////////////////////////////////
|
||||
Coordinate ldims = grid->LocalDimensions();
|
||||
Coordinate gdims = grid->GlobalDimensions();
|
||||
myLcoor.resize(lsites);
|
||||
myGsite.resize(lsites);
|
||||
for(int ss=0; ss<lsites; ss++){
|
||||
Coordinate lcoor(nd);
|
||||
Lexicographic::CoorFromIndex(lcoor, ss, ldims);
|
||||
Coordinate gcoor(nd);
|
||||
for(int d=0; d<nd; d++) gcoor[d] = grid->_lstart[d] + lcoor[d];
|
||||
int64_t gsite;
|
||||
Lexicographic::IndexFromCoor(gcoor, gsite, gdims);
|
||||
myLcoor[ss] = lcoor;
|
||||
myGsite[ss] = gsite;
|
||||
}
|
||||
|
||||
slab.resize((uint64_t)nrows * N);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// The only place a coarse operator is needed: pull its elements, invert,
|
||||
// and make the slab resident. Any class exposing Geometry() and
|
||||
// ExtractMatrix(p,A) will do -- single RHS or either multiRHS.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
template<class CoarseOp>
|
||||
void Import(CoarseOp &Op)
|
||||
{
|
||||
double t0 = usecond();
|
||||
{
|
||||
CheckMrhsSlices(Op); // the batched apply keeps its slices apart
|
||||
ImportDense(Op); // slab <- my rows of A (LOCAL, no comms)
|
||||
ImportCertificate(Op); // dense apply == Op.M, before inversion
|
||||
InvertDense(Op); // slab <- my rows of A^{-1}
|
||||
double t1 = usecond();
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: import+invert took "
|
||||
<< (t1-t0)/1.0e6 << " s" << std::endl;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Device residency + persistent apply buffers + AOT split-K pointers
|
||||
////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
uint64_t sbytes = (uint64_t)nrows * N * sizeof(ComplexF);
|
||||
dSlab.resize((uint64_t)nrows*N);
|
||||
acceleratorCopyToDevice(&slab[0],&dSlab[0],sbytes);
|
||||
|
||||
// Split-K chunk count, snapped DOWN to a divisor of N.
|
||||
NK = 1;
|
||||
for(int j=1;j<=SPLITK;j++) if ( (N % j) == 0 ) NK = j;
|
||||
int64_t Kc = N / NK;
|
||||
|
||||
dX.resize((uint64_t)N*MRHS_MAX);
|
||||
dY.resize((uint64_t)nrows*MRHS_MAX);
|
||||
dPartial.resize((uint64_t)NK*nrows*MRHS_MAX);
|
||||
hX.resize((uint64_t)N*MRHS_MAX);
|
||||
hY.resize((uint64_t)nrows*MRHS_MAX);
|
||||
|
||||
aptrs.resize(NK); xptrs.resize(NK); cptrs.resize(NK);
|
||||
std::vector<ComplexF*> h(NK);
|
||||
for(int j=0;j<NK;j++) h[j] = &dSlab[0] + (uint64_t)j*Kc; // K-offset, lda=N
|
||||
acceleratorCopyToDevice(&h[0],&aptrs[0],NK*sizeof(ComplexF*));
|
||||
for(int j=0;j<NK;j++) h[j] = &dX[0] + (uint64_t)j*Kc; // K-offset, ldb=N
|
||||
acceleratorCopyToDevice(&h[0],&xptrs[0],NK*sizeof(ComplexF*));
|
||||
for(int j=0;j<NK;j++) h[j] = &dPartial[0] + (uint64_t)j*nrows*MRHS_MAX; // compact, ldc=nrows
|
||||
acceleratorCopyToDevice(&h[0],&cptrs[0],NK*sizeof(ComplexF*));
|
||||
|
||||
{
|
||||
dG.resize((uint64_t)N*MRHS_MAX);
|
||||
// allgather delivers blocks in lexicographic-coordinate order; the row
|
||||
// blocks of x are in RANK order. Same table as BuildRankMajorMap.
|
||||
int P = grid->ProcessorCount();
|
||||
std::vector<int> l2r(P);
|
||||
for(int lp=0; lp<P; lp++){ Coordinate pc(nd); Lexicographic::CoorFromIndex(pc, lp, grid->_processors); l2r[lp] = grid->RankFromProcessorCoor(pc); }
|
||||
dLex2Rank.resize(P);
|
||||
acceleratorCopyToDevice(&l2r[0], &dLex2Rank[0], P*sizeof(int));
|
||||
myLex = CartesianLexIndex(grid);
|
||||
GRID_ASSERT( l2r[myLex] == grid->ThisRank() );
|
||||
// x and the slab columns are in GLOBAL-SITE order (hX[myGsite*nbasis+b]);
|
||||
// the gathered blocks are in RANK-MAJOR order (rank*nrows + ss*nbasis + b).
|
||||
// The two coincide only on one rank, so scatter through the inverse map.
|
||||
std::vector<int64_t> g2rm; BuildRankMajorMap(g2rm);
|
||||
std::vector<int64_t> rm2g(N); for(int64_t g=0; g<N; g++) rm2g[g2rm[g]] = g;
|
||||
for(int ss=0; ss<lsites; ss++) GRID_ASSERT( rm2g[(int64_t)grid->ThisRank()*nrows + (int64_t)ss*nbasis] == myGsite[ss]*nbasis );
|
||||
dRm2G.resize(N);
|
||||
acceleratorCopyToDevice(&rm2g[0], &dRm2G[0], N*sizeof(int64_t));
|
||||
}
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: slab resident on device ("
|
||||
<< sbytes/1024./1024. << " MB/rank), split-K NK=" << NK << " (Kc=" << Kc
|
||||
<< "); DEVICE cartesian ring ALLGATHER (P2P)" << std::endl;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// VERIFY: || A (Ainv x) - x || / ||x|| through the DEVICE split-K core.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
{
|
||||
Field x(grid); Field y(grid); Field z(grid);
|
||||
x = CoarseScalar(1.0,0.0);
|
||||
double ta = usecond();
|
||||
(*this)(x, y);
|
||||
double tb = usecond();
|
||||
ApplyOracle(Op, y, z);
|
||||
z = z - x;
|
||||
RealD rel = std::sqrt(norm2(z)/norm2(x));
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: VERIFY ||A Ainv x - x||/||x|| = "
|
||||
<< rel << " (one apply took " << (tb-ta)/1000.0 << " ms)" << std::endl;
|
||||
GRID_ASSERT(rel < 1.0e-2);
|
||||
}
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: setup complete, total "
|
||||
<< (usecond()-t0)/1.0e6 << " s" << std::endl;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Apply the source operator to a D dimensional field, whichever kind it is.
|
||||
//
|
||||
// A multiRHS operator lives on the D+1 grid, so drive it with several right
|
||||
// hand sides at once: slice r carries (r+1)*in, and linearity says the
|
||||
// results must scale likewise. One apply, and unlike a single rhs check it
|
||||
// also catches rhs mixing. Cheap check, not a production path.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
template<class CoarseOp>
|
||||
void ApplyOracle(CoarseOp &Op,const Field &in, Field &out)
|
||||
{
|
||||
if ( Op.Grid() == grid ) { Op.M(in,out); return; }
|
||||
|
||||
GridBase *mgrid = Op.Grid();
|
||||
GRID_ASSERT(mgrid->_ndimension == nd+1);
|
||||
int nr = mgrid->_fdimensions[0];
|
||||
|
||||
// Every slice carries the same vector; slice 0 is the answer. The
|
||||
// slices are not a check here -- see CheckMrhsSlices, which does that
|
||||
// once on a field chosen for the purpose.
|
||||
Field min(mgrid), mout(mgrid);
|
||||
for(int r=0;r<nr;r++) InsertSliceFast(in,min,r,0);
|
||||
Op.M(min,mout);
|
||||
ExtractSliceFast(out,mout,0,0);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Do the multiRHS slices stay independent? Slice r carries (r+1) times
|
||||
// one RANDOM field, so linearity says the results scale likewise, and a
|
||||
// slot reading another slot's data is an O(1) error.
|
||||
//
|
||||
// The field is random on purpose. Run on the output of the inverse, as
|
||||
// this check once was, it measures the cancellation in A applied to
|
||||
// A^-1 x instead: at the production point that is 1.3e-4 in an fp32
|
||||
// sector with an fp32 inversion, so the check then fires on any change
|
||||
// of summation order while saying nothing about slice independence.
|
||||
// Once per import, not per apply.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
template<class CoarseOp>
|
||||
void CheckMrhsSlices(CoarseOp &Op)
|
||||
{
|
||||
if ( Op.Grid() == grid ) return; // single rhs: nothing to mix
|
||||
|
||||
GridBase *mgrid = Op.Grid();
|
||||
GRID_ASSERT(mgrid->_ndimension == nd+1);
|
||||
int nr = mgrid->_fdimensions[0];
|
||||
if ( nr < 2 ) return;
|
||||
|
||||
Field in(grid);
|
||||
GridParallelRNG rng(grid); rng.SeedFixedIntegers(std::vector<int>({7,8,9,10}));
|
||||
random(rng,in);
|
||||
|
||||
Field min(mgrid), mout(mgrid);
|
||||
for(int r=0;r<nr;r++){
|
||||
Field scaled(grid);
|
||||
scaled = CoarseScalar(r+1.0,0.0)*in;
|
||||
InsertSliceFast(scaled,min,r,0);
|
||||
}
|
||||
Op.M(min,mout);
|
||||
|
||||
Field s0(grid);
|
||||
ExtractSliceFast(s0,mout,0,0);
|
||||
RealD worst=0.0;
|
||||
for(int r=1;r<nr;r++){
|
||||
Field sr(grid),d(grid);
|
||||
ExtractSliceFast(sr,mout,r,0);
|
||||
d = sr - CoarseScalar(r+1.0,0.0)*s0;
|
||||
RealD rel = std::sqrt(norm2(d)/norm2(sr));
|
||||
worst = std::max(worst,rel);
|
||||
const RealD otol = (sizeof(CoarseScalar)==sizeof(ComplexF)) ? 1.0e-4 : 1.0e-6;
|
||||
if ( rel >= otol ) {
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: mrhs slice "<<r
|
||||
<<" inconsistent with slice 0, rel "<<rel<<std::endl;
|
||||
}
|
||||
GRID_ASSERT( rel < otol );
|
||||
}
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: mrhs slices independent to "
|
||||
<< worst << " over " << nr << " right hand sides" << std::endl;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// 1. Direct stencil -> dense import of MY ROWS of A (no comms):
|
||||
// Dense[(s,a),(wrap(s+shift_p),b)] += A[p][s]_{a,b}
|
||||
////////////////////////////////////////////////////////////////////
|
||||
template<class CoarseOp>
|
||||
void ImportDense(CoarseOp &Op)
|
||||
{
|
||||
double t = -usecond();
|
||||
Coordinate gdims = grid->GlobalDimensions();
|
||||
|
||||
uint64_t nelem = (uint64_t)nrows * N;
|
||||
thread_for(i, nelem, { slab[i] = ComplexF(0.0,0.0); });
|
||||
|
||||
for(int p=0; p<Op.Geometry().npoint; p++){
|
||||
Coordinate shift = Op.Geometry().shifts[p];
|
||||
// ExtractMatrix hands back the unpadded matrix field, which is what the
|
||||
// unpadded coordinates below index.
|
||||
CoarseMatrix Aun(grid); Op.ExtractMatrix(p,Aun);
|
||||
autoView(Av, Aun, CpuRead);
|
||||
thread_for(ss, lsites, {
|
||||
Coordinate ncoor(nd);
|
||||
for(int d=0; d<nd; d++){
|
||||
int64_t g = grid->_lstart[d] + myLcoor[ss][d] + shift[d];
|
||||
ncoor[d] = (int)((g % gdims[d] + gdims[d]) % gdims[d]);
|
||||
}
|
||||
int64_t nsite;
|
||||
Lexicographic::IndexFromCoor(ncoor, nsite, gdims);
|
||||
Msobj m;
|
||||
peekLocalSite(m, Av, myLcoor[ss]);
|
||||
CoarseScalar *md = (CoarseScalar *)&m;
|
||||
// The operator contracts out(s,b) = sum_a A[p](s)(a,b) in(nbr,a)
|
||||
// (GeneralCoarsenedMatrix.h Mult kernel): the stored site matrix
|
||||
// acts TRANSPOSED, so element (a,b) lands at dense row (s,b),
|
||||
// column (nbr,a).
|
||||
for(int b=0; b<nbasis; b++){
|
||||
ComplexF *row = &slab[(uint64_t)(ss*nbasis+b)*N + nsite*nbasis];
|
||||
for(int a=0; a<nbasis; a++)
|
||||
row[a] += ComplexF(md[a*nbasis+b]); // += : wrapped shifts may collide
|
||||
}
|
||||
});
|
||||
}
|
||||
t += usecond();
|
||||
|
||||
// Structural diagnostic: identically-zero rows of my slab (a healthy
|
||||
// coarse operator has none; dead rows mean a rank-deficient import
|
||||
// or operator and the inverse will be NaN).
|
||||
int64_t zrows = 0;
|
||||
for(int64_t r=0; r<nrows; r++)
|
||||
{
|
||||
double mx = 0.0;
|
||||
const ComplexF *row = &slab[(uint64_t)r*N];
|
||||
for(int64_t j=0; j<N; j++)
|
||||
{
|
||||
mx = std::max(mx, (double)abs(row[j]));
|
||||
}
|
||||
if ( mx < 1.0e-30 ) zrows++;
|
||||
}
|
||||
RealD gz = (RealD)zrows;
|
||||
grid->GlobalSumVector(&gz, 1);
|
||||
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: stencil->dense import took "
|
||||
<< t/1.0e6 << " s (" << Op.Geometry().npoint << " points, local, no comms)"
|
||||
<< " zero rows " << (int64_t)gz << "/" << N << std::endl;
|
||||
|
||||
// Debug: coordinate pattern of live sites (mechanism fingerprint)
|
||||
if ( (int64_t)gz > 0 )
|
||||
{
|
||||
int shown = 0;
|
||||
for(int ss=0; ss<lsites && shown<24; ss++)
|
||||
{
|
||||
double mx = 0.0;
|
||||
const ComplexF *row = &slab[(uint64_t)(ss*nbasis)*N];
|
||||
for(int64_t j=0; j<N; j++)
|
||||
{
|
||||
mx = std::max(mx, (double)abs(row[j]));
|
||||
}
|
||||
if ( mx > 1.0e-30 )
|
||||
{
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: LIVE site ss=" << ss
|
||||
<< " lcoor " << myLcoor[ss] << std::endl;
|
||||
shown++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// 2. IMPORT CERTIFICATE: dense rows vs Op.M on a NON-CONSTANT vector.
|
||||
// (Constant x has x[s+d]==x[s-d]: blind to a shift-sign error.)
|
||||
////////////////////////////////////////////////////////////////////
|
||||
template<class CoarseOp>
|
||||
void ImportCertificate(CoarseOp &Op)
|
||||
{
|
||||
Field x(grid); Field Ax(grid); Field Dx(grid);
|
||||
for(int ss=0; ss<lsites; ss++){
|
||||
sobj s;
|
||||
for(int b=0; b<nbasis; b++){
|
||||
double ph = 0.37*(double)(myGsite[ss]*nbasis+b);
|
||||
((CoarseScalar *)&s)[b] = CoarseScalar(std::cos(ph),std::sin(0.61*ph));
|
||||
}
|
||||
pokeLocalSite(s, x, myLcoor[ss]);
|
||||
}
|
||||
// gather full x (zero-fill + exact GlobalSum), dense rows on host
|
||||
std::vector<ComplexD> xh((uint64_t)N, ComplexD(0.0,0.0));
|
||||
for(int ss=0; ss<lsites; ss++){
|
||||
sobj s;
|
||||
peekLocalSite(s, x, myLcoor[ss]);
|
||||
for(int b=0; b<nbasis; b++) xh[ myGsite[ss]*nbasis + b ] = ComplexD(((CoarseScalar *)&s)[b]);
|
||||
}
|
||||
grid->GlobalSumVector(&xh[0], (int)N);
|
||||
std::vector<ComplexD> yh(nrows);
|
||||
thread_for(r, nrows, {
|
||||
ComplexD acc(0.0,0.0);
|
||||
const ComplexF *row = &slab[(uint64_t)r * N];
|
||||
for(int64_t j=0; j<N; j++) acc += ComplexD(row[j]) * xh[j];
|
||||
yh[r] = acc;
|
||||
});
|
||||
for(int ss=0; ss<lsites; ss++){
|
||||
sobj s;
|
||||
for(int b=0; b<nbasis; b++) ((CoarseScalar *)&s)[b] = CoarseScalar(yh[ss*nbasis+b]);
|
||||
pokeLocalSite(s, Dx, myLcoor[ss]);
|
||||
}
|
||||
ApplyOracle(Op, x, Ax);
|
||||
Field d(grid); d = Dx - Ax;
|
||||
RealD rel = std::sqrt(norm2(d)/norm2(Ax));
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: IMPORT CERTIFICATE ||Dense x - A x||/||A x|| = "
|
||||
<< rel << std::endl;
|
||||
if ( rel >= 1.0e-3 ) {
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: IMPORT CERTIFICATE FAILED. If O(1), the "
|
||||
<< "stencil shift-sign convention of the coarse operator has changed: "
|
||||
<< "the import in ImportDense/ImportDenseForInversion must change with it"
|
||||
<< std::endl;
|
||||
}
|
||||
GRID_ASSERT(rel < 1.0e-3);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// 3a. Global column -> rank-major column map, computed LOCALLY.
|
||||
// Rank-major ordering: rank q's rows/columns are the contiguous
|
||||
// block [q*nrows, (q+1)*nrows), ordered by q's local site index
|
||||
// (uniform local volumes make ownership arithmetic exact).
|
||||
// MPI_Cart_rank is queried ONCE PER RANK (serial, P calls) into a
|
||||
// lex-processor table; the per-site sweep is then pure arithmetic.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void BuildRankMajorMap(std::vector<int64_t> &g2rm)
|
||||
{
|
||||
int P = grid->ProcessorCount();
|
||||
Coordinate pdims = grid->_processors;
|
||||
Coordinate gdims = grid->GlobalDimensions();
|
||||
Coordinate ldims = grid->LocalDimensions();
|
||||
|
||||
std::vector<int> lexp2rank(P);
|
||||
for(int lp=0; lp<P; lp++)
|
||||
{
|
||||
Coordinate pcoor(nd);
|
||||
Lexicographic::CoorFromIndex(pcoor, lp, pdims);
|
||||
lexp2rank[lp] = grid->RankFromProcessorCoor(pcoor);
|
||||
}
|
||||
|
||||
int64_t gsites = grid->gSites();
|
||||
g2rm.resize(N);
|
||||
thread_for(gsite, gsites, {
|
||||
Coordinate gcoor(nd);
|
||||
Coordinate pcoor(nd);
|
||||
Coordinate lcoor(nd);
|
||||
Lexicographic::CoorFromIndex(gcoor, gsite, gdims);
|
||||
for(int d=0; d<nd; d++)
|
||||
{
|
||||
pcoor[d] = gcoor[d]/ldims[d];
|
||||
lcoor[d] = gcoor[d]-pcoor[d]*ldims[d];
|
||||
}
|
||||
int64_t lexp;
|
||||
int64_t lsite;
|
||||
Lexicographic::IndexFromCoor(pcoor, lexp, pdims);
|
||||
Lexicographic::IndexFromCoor(lcoor, lsite, ldims);
|
||||
int64_t base = (int64_t)lexp2rank[lexp]*nrows + lsite*nbasis;
|
||||
for(int b=0; b<nbasis; b++)
|
||||
{
|
||||
g2rm[(uint64_t)gsite*nbasis + b] = base + b;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// 3b. Direct stencil -> rank-major import of MY ROWS of A in the
|
||||
// inversion precision (DenseInverseScalar): the stencil source is
|
||||
// read once, straight into the buffer the Schur recursion factorises,
|
||||
// never via the fp32 apply slab. Same
|
||||
// loop/sign/accumulate/transposed-contraction discipline as
|
||||
// ImportDense; output is column-major rows x N with columns in
|
||||
// rank-major order (g2rm).
|
||||
// ALWAYS-ON CERTIFICATE: this import, rounded, must agree with
|
||||
// the fp32 slab entry at the corresponding global column, over the
|
||||
// WHOLE of my rows (few ulp: wrapped-shift collisions accumulate in
|
||||
// different precision order). NaN-proof: non-finite entries are
|
||||
// counted explicitly since max() silently masks NaN.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
template<class CoarseOp>
|
||||
void ImportDenseForInversion(CoarseOp &Op, BlockRows &S, std::vector<int64_t> &g2rm)
|
||||
{
|
||||
Coordinate gdims = grid->GlobalDimensions();
|
||||
|
||||
std::vector<DenseInverseScalar> h((uint64_t)nrows*N, DenseInverseScalar(0.0,0.0));
|
||||
for(int p=0; p<Op.Geometry().npoint; p++)
|
||||
{
|
||||
Coordinate shift = Op.Geometry().shifts[p];
|
||||
CoarseMatrix Aun(grid); Op.ExtractMatrix(p,Aun);
|
||||
autoView(Av, Aun, CpuRead);
|
||||
thread_for(ss, lsites, {
|
||||
Coordinate ncoor(nd);
|
||||
for(int d=0; d<nd; d++)
|
||||
{
|
||||
int64_t g = grid->_lstart[d] + myLcoor[ss][d] + shift[d];
|
||||
ncoor[d] = (int)((g % gdims[d] + gdims[d]) % gdims[d]);
|
||||
}
|
||||
int64_t nsite;
|
||||
Lexicographic::IndexFromCoor(ncoor, nsite, gdims);
|
||||
Msobj m;
|
||||
peekLocalSite(m, Av, myLcoor[ss]);
|
||||
CoarseScalar *md = (CoarseScalar *)&m;
|
||||
// Transposed contraction as ImportDense: (a,b) lands at
|
||||
// row (s,b), column (nbr,a); column index in rank-major order.
|
||||
for(int a=0; a<nbasis; a++)
|
||||
{
|
||||
int64_t jj = g2rm[ nsite*nbasis + a ];
|
||||
for(int b=0; b<nbasis; b++)
|
||||
{
|
||||
h[(uint64_t)(ss*nbasis+b) + (uint64_t)jj*nrows] += DenseInverseScalar(md[a*nbasis+b]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Certificate vs the fp32 slab (slab holds A at this point)
|
||||
double mx = 0.0;
|
||||
int64_t nbad = 0;
|
||||
for(int64_t i=0; i<nrows; i++)
|
||||
{
|
||||
for(int64_t gcol=0; gcol<N; gcol++)
|
||||
{
|
||||
ComplexD d64 = ComplexD(h[(uint64_t)(i + g2rm[gcol]*nrows)]);
|
||||
ComplexF f32 = slab[(uint64_t)i*N + gcol];
|
||||
double dev = abs(ComplexD(f32) - d64);
|
||||
if ( !std::isfinite(dev) ) nbad++;
|
||||
else mx = std::max(mx, dev);
|
||||
}
|
||||
}
|
||||
RealD gmx = mx;
|
||||
RealD gbad = (RealD)nbad;
|
||||
grid->GlobalMax(gmx);
|
||||
grid->GlobalSumVector(&gbad, 1);
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: inversion-source import certificate "
|
||||
<< "max|A_inv - A_slab| = " << gmx
|
||||
<< " non-finite entries " << (int64_t)gbad << std::endl;
|
||||
GRID_ASSERT( gbad == 0 );
|
||||
GRID_ASSERT( gmx < 1.0e-5 );
|
||||
|
||||
S.Resize(nrows, N);
|
||||
acceleratorCopyToDevice(&h[0], &S.data[0], (uint64_t)nrows*N*sizeof(DenseInverseScalar));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// 3c. The inverse: distributed recursive Schur, end to end in the
|
||||
// inversion precision (DenseInverseScalar, a configure-time choice):
|
||||
// stencil -> rank-major import -> recursion -> ONE terminal rounding
|
||||
// into the fp32 apply slab. Everything downstream (device
|
||||
// residency, split-K apply, VERIFY) is fp32 regardless.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
template<class CoarseOp>
|
||||
void InvertDense(CoarseOp &Op)
|
||||
{
|
||||
double t1 = usecond();
|
||||
int P = grid->ProcessorCount();
|
||||
int me = grid->ThisRank();
|
||||
|
||||
// Uniform local volumes => contiguous uniform ownership
|
||||
std::vector<int64_t> rowStart(P+1);
|
||||
for(int r=0; r<=P; r++)
|
||||
{
|
||||
rowStart[r] = (int64_t)r*nrows;
|
||||
}
|
||||
GRID_ASSERT( rowStart[P] == N );
|
||||
|
||||
std::vector<int64_t> g2rm;
|
||||
BuildRankMajorMap(g2rm);
|
||||
|
||||
// Self-certifying map: my own global rows land at my rank-major slots
|
||||
for(int ss=0; ss<lsites; ss++)
|
||||
{
|
||||
for(int a=0; a<nbasis; a++)
|
||||
{
|
||||
GRID_ASSERT( g2rm[ myGsite[ss]*nbasis + a ] == (int64_t)me*nrows + ss*nbasis + a );
|
||||
}
|
||||
}
|
||||
|
||||
BlockRows S;
|
||||
ImportDenseForInversion(Op, S, g2rm);
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// The 2D block-cyclic recursion (BlockCyclicSchurInverse).
|
||||
// Everything in it -- redistribution, SUMMA rings, leaves -- is
|
||||
// point-to-point SendToRecvFrom; no collectives at all. Block size
|
||||
// nb = rows-per-rank makes the redistribution edges maximally
|
||||
// regular.
|
||||
////////////////////////////////////////////////////////////////
|
||||
double t2, t3;
|
||||
{
|
||||
int Pr,Pc;
|
||||
BlockCyclicLayout::ChooseProcessGrid(P, Pr, Pc);
|
||||
int64_t nb = nrows;
|
||||
GRID_ASSERT( nb >= 1 );
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: 2D SCHUR invert, process grid "
|
||||
<< Pr << " x " << Pc << " nb " << nb
|
||||
<< " (pure P2P: redistribute + SUMMA rings + local leaves)" << std::endl;
|
||||
BlockCyclicMatrix A2(grid, N, nb, Pr, Pc);
|
||||
BlockCyclicSchurInverse RSI2;
|
||||
t2 = usecond();
|
||||
BlockCyclicRedistribute::RowsToCyclic(grid, rowStart, &S.data[0], nrows, A2);
|
||||
RSI2.Invert(A2);
|
||||
BlockCyclicRedistribute::CyclicToRows(grid, rowStart, A2, &S.data[0], nrows);
|
||||
t3 = usecond();
|
||||
RSI2.ReportTelemetry(grid);
|
||||
}
|
||||
|
||||
// The single terminal rounding: fp64 inverse -> fp32 apply slab
|
||||
// (row-major, global columns)
|
||||
{
|
||||
std::vector<DenseInverseScalar> h((uint64_t)nrows*N);
|
||||
acceleratorCopyFromDevice(&S.data[0], &h[0], (uint64_t)nrows*N*sizeof(DenseInverseScalar));
|
||||
thread_for(gcol, N, {
|
||||
int64_t jj = g2rm[gcol];
|
||||
for(int64_t i=0; i<nrows; i++)
|
||||
{
|
||||
slab[(uint64_t)i*N + gcol] = ComplexF(h[(uint64_t)(i + jj*nrows)]);
|
||||
}
|
||||
});
|
||||
}
|
||||
double t4 = usecond();
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: SCHUR "
|
||||
<< (sizeof(DenseInverseScalar)==sizeof(ComplexF) ? "fp32" : "fp64")
|
||||
<< " distributed invert took "
|
||||
<< (t4-t1)/1.0e6 << " s (recursion " << (t3-t2)/1.0e6 << " s)" << std::endl;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// CORE apply on packed data: hX[N x nr] (zero-filled, my sites only)
|
||||
// -> ring allgather -> split-K GEMM against the resident slab ->
|
||||
// reduce partials -> hY[nrows x nr] (column major). Platform-agnostic:
|
||||
// deviceVector + GridBLAS (Eigen fallback on CPU builds).
|
||||
// tprof (optional): per-phase microseconds {allgather, H2D, gemm+reduce,
|
||||
// D2H}, printed by the caller on GridLogPerformance.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void SlabApplyPacked(int nr, double *tprof)
|
||||
{
|
||||
GRID_TRACE("DenseSlabApply");
|
||||
GRID_ASSERT(nr <= MRHS_MAX);
|
||||
uint64_t nY = (uint64_t)nrows * nr;
|
||||
int64_t Kc = N / NK;
|
||||
double t1 = usecond();
|
||||
double t2, t3;
|
||||
{
|
||||
// ALLGATHER: x is not a reduction -- every rank owns the rows of x at
|
||||
// global columns myGsite[ss]*nbasis+b (scattered by site coordinate, NOT
|
||||
// a contiguous block) and needs all of it. Only MY rows go host->device
|
||||
// (nrows x nr, ~15 KB at nr=1), packed rank-major [r][ss*nbasis+b],
|
||||
// gathered along the process grid, then scattered through rm2g into the
|
||||
// column-major dX (ld = N, global-site columns) the split-K GEMM reads.
|
||||
const uint64_t chunk = (uint64_t)nrows*nr; // my block: [r][ss*nbasis+b]
|
||||
{ GRID_TRACE("DenseH2D");
|
||||
std::vector<ComplexF> hG(chunk);
|
||||
for(int r=0;r<nr;r++)
|
||||
for(int ss=0; ss<lsites; ss++)
|
||||
memcpy(&hG[(uint64_t)r*nrows + (uint64_t)ss*nbasis], &hX[(uint64_t)r*N + (uint64_t)myGsite[ss]*nbasis], nbasis*sizeof(ComplexF));
|
||||
acceleratorCopyToDevice(&hG[0], &dG[(uint64_t)myLex*chunk], chunk*sizeof(ComplexF)); // my slot is my LEX index
|
||||
}
|
||||
t2 = usecond();
|
||||
{ GRID_TRACE("DenseAllgather");
|
||||
CartesianRingAllGather(grid, (ComplexF *)&dG[0], chunk);
|
||||
// scatter lex block L=[r][i] -> dX[r*N + rm2g[rank(L)*nrows + i]]
|
||||
ComplexF *g = &dG[0]; ComplexF *x = &dX[0]; int *l2r = &dLex2Rank[0]; int64_t *rm2g = &dRm2G[0];
|
||||
const int64_t nrw = nrows; const int64_t NN = N; const int nrr = nr;
|
||||
accelerator_for(idx, (uint64_t)N*nr, 1, {
|
||||
int64_t r = idx / NN; int64_t gi = idx - r*NN;
|
||||
int64_t L = gi / nrw; int64_t i = gi - L*nrw;
|
||||
x[r*NN + rm2g[(int64_t)l2r[L]*nrw + i]] = g[L*(nrw*nrr) + r*nrw + i];
|
||||
});
|
||||
}
|
||||
t3 = usecond();
|
||||
}
|
||||
// Y = op(slab,T) . X : row-major slab (nrows x N) == col-major A^T
|
||||
// (N x nrows, lda=N) => transpose gives the nrows x N operator.
|
||||
// Split-K: NK chunk-GEMMs by pointer offset (AOT lists), then reduce.
|
||||
ComplexF one (1.0,0.0);
|
||||
ComplexF zero(0.0,0.0);
|
||||
{ GRID_TRACE("DenseSplitKGEMM");
|
||||
BLAS.gemmBatched(GridBLAS_OP_T, GridBLAS_OP_N,
|
||||
(int)nrows, nr, (int)Kc,
|
||||
one, aptrs, (int)N,
|
||||
xptrs, (int)N,
|
||||
zero, cptrs, (int)nrows);
|
||||
BLAS.synchronise();
|
||||
ComplexF *pp = &dPartial[0];
|
||||
ComplexF *py = &dY[0];
|
||||
uint64_t stride = (uint64_t)nrows*MRHS_MAX;
|
||||
int nk = NK;
|
||||
accelerator_for(i, nY, 1, {
|
||||
ComplexF acc(0.0,0.0);
|
||||
for(int j=0;j<nk;j++) acc += pp[(uint64_t)j*stride + i];
|
||||
py[i] = acc;
|
||||
});
|
||||
}
|
||||
double t4 = usecond();
|
||||
{ GRID_TRACE("DenseD2H");
|
||||
acceleratorCopyFromDevice(&dY[0],&hY[0],nY*sizeof(ComplexF));
|
||||
}
|
||||
double t5 = usecond();
|
||||
if (tprof) {
|
||||
tprof[0] = t3-t2; // allgather
|
||||
tprof[1] = t2-t1; // H2D
|
||||
tprof[2] = t4-t3; // gemm+reduce
|
||||
tprof[3] = t5-t4; // D2H
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Single-RHS apply (also the VERIFY path => certifies device slab).
|
||||
////////////////////////////////////////////////////////////////////
|
||||
virtual void operator()(const Field &src, Field &psi)
|
||||
{
|
||||
GRID_TRACE("DenseApply1");
|
||||
uint64_t nX = (uint64_t)N;
|
||||
{ GRID_TRACE("DensePack");
|
||||
thread_for(i, nX, { hX[i]=ComplexF(0.0,0.0); });
|
||||
for(int ss=0; ss<lsites; ss++){
|
||||
sobj s;
|
||||
peekLocalSite(s, src, myLcoor[ss]);
|
||||
for(int b=0; b<nbasis; b++)
|
||||
hX[ myGsite[ss]*nbasis + b ] = ComplexF(((CoarseScalar *)&s)[b]);
|
||||
}
|
||||
}
|
||||
SlabApplyPacked(1, nullptr);
|
||||
{ GRID_TRACE("DenseUnpack");
|
||||
for(int ss=0; ss<lsites; ss++){
|
||||
sobj s;
|
||||
for(int b=0; b<nbasis; b++)
|
||||
((CoarseScalar *)&s)[b] = CoarseScalar(hY[ss*nbasis + b]);
|
||||
pokeLocalSite(s, psi, myLcoor[ss]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Defect of an applied inverse. Was a DENSE_CC_CHECK block inside
|
||||
// operator(), but that is virtual and cannot take an operator, so the
|
||||
// caller now asks for it explicitly.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
template<class CoarseOp>
|
||||
void CheckApply(CoarseOp &Op,const Field &src,const Field &psi)
|
||||
{
|
||||
Field tmp(grid);
|
||||
Op.M(psi, tmp);
|
||||
tmp = tmp - src;
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: apply defect ||A x - b||/||b|| = "
|
||||
<< std::sqrt(norm2(tmp)/norm2(src)) << std::endl;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Batched (vector-of-fields) apply.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void ApplyBatch(std::vector<Field> &src, std::vector<Field> &psi)
|
||||
{
|
||||
int nr = src.size();
|
||||
GRID_ASSERT(nr <= MRHS_MAX);
|
||||
double t0 = usecond();
|
||||
uint64_t nX = (uint64_t)N*nr;
|
||||
{ GRID_TRACE("DensePack");
|
||||
thread_for(i, nX, { hX[i]=ComplexF(0.0,0.0); });
|
||||
for(int rr=0; rr<nr; rr++){
|
||||
for(int ss=0; ss<lsites; ss++){
|
||||
sobj s;
|
||||
peekLocalSite(s, src[rr], myLcoor[ss]);
|
||||
for(int b=0; b<nbasis; b++)
|
||||
hX[ (uint64_t)rr*N + myGsite[ss]*nbasis + b ] = ComplexF(((CoarseScalar *)&s)[b]);
|
||||
}
|
||||
}
|
||||
}
|
||||
SlabApplyPacked(nr, nullptr);
|
||||
{ GRID_TRACE("DenseUnpack");
|
||||
for(int rr=0; rr<nr; rr++){
|
||||
for(int ss=0; ss<lsites; ss++){
|
||||
sobj s;
|
||||
for(int b=0; b<nbasis; b++)
|
||||
((CoarseScalar *)&s)[b] = CoarseScalar(hY[(uint64_t)rr*nrows + (ss*nbasis+b)]);
|
||||
pokeLocalSite(s, psi[rr], myLcoor[ss]);
|
||||
}
|
||||
}
|
||||
}
|
||||
double t1 = usecond();
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: batched apply " << nr << " rhs took "
|
||||
<< (t1-t0)/1000.0 << " ms (" << (t1-t0)/1000.0/nr << " ms/rhs)" << std::endl;
|
||||
}
|
||||
|
||||
template<class CoarseOp>
|
||||
void CheckApplyBatch(CoarseOp &Op,std::vector<Field> &src,std::vector<Field> &psi,int nr)
|
||||
{
|
||||
Field tmp(grid);
|
||||
for(int rr=0; rr<nr; rr++){
|
||||
Op.M(psi[rr], tmp);
|
||||
tmp = tmp - src[rr];
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: batch defect["<<rr<<"] = "
|
||||
<< std::sqrt(norm2(tmp)/norm2(src[rr])) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// 6D mrhs apply: operates DIRECTLY on the packed 6D field (rhs = dim 0).
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void ApplyBatch6D(const Field &in6, Field &out6, int nr)
|
||||
{
|
||||
GRID_ASSERT(nr <= MRHS_MAX);
|
||||
GRID_ASSERT(in6.Grid()->_ndimension == nd+1); // {rhs, s, x,y,z,t}
|
||||
double t0 = usecond();
|
||||
Field &in = const_cast<Field &>(in6);
|
||||
uint64_t nX = (uint64_t)N * nr;
|
||||
thread_for(i, nX, { hX[i]=ComplexF(0.0,0.0); });
|
||||
{ GRID_TRACE("DensePack");
|
||||
autoView(iv, in, CpuRead);
|
||||
Coordinate c6(nd+1);
|
||||
for(int ss=0; ss<lsites; ss++){
|
||||
for(int d=0; d<nd; d++) c6[d+1] = myLcoor[ss][d];
|
||||
for(int rr=0; rr<nr; rr++){
|
||||
c6[0] = rr;
|
||||
sobj s;
|
||||
peekLocalSite(s, iv, c6);
|
||||
for(int b=0; b<nbasis; b++)
|
||||
hX[(uint64_t)rr*N + myGsite[ss]*nbasis + b] = ComplexF(((CoarseScalar *)&s)[b]);
|
||||
}
|
||||
}
|
||||
}
|
||||
double t1 = usecond();
|
||||
double tprof[4];
|
||||
SlabApplyPacked(nr, tprof);
|
||||
double t5 = usecond();
|
||||
{ GRID_TRACE("DenseUnpack");
|
||||
autoView(ov, out6, CpuWrite);
|
||||
Coordinate c6(nd+1);
|
||||
for(int ss=0; ss<lsites; ss++){
|
||||
for(int d=0; d<nd; d++) c6[d+1] = myLcoor[ss][d];
|
||||
for(int rr=0; rr<nr; rr++){
|
||||
c6[0] = rr;
|
||||
sobj s;
|
||||
for(int b=0; b<nbasis; b++)
|
||||
((CoarseScalar *)&s)[b] = CoarseScalar(hY[(uint64_t)rr*nrows + (ss*nbasis+b)]); // Y col-major
|
||||
pokeLocalSite(s, ov, c6);
|
||||
}
|
||||
}
|
||||
}
|
||||
double t6 = usecond();
|
||||
std::cout << GridLogMessage << "DenseCoarseMatrix: apply6D " << nr << " rhs took "
|
||||
<< (t6-t0)/1000.0 << " ms" << std::endl;
|
||||
std::cout << GridLogPerformance << "DenseCoarseMatrix: apply6D profile:"
|
||||
<< " pack " << (t1-t0)/1000.0
|
||||
<< " allgather " << tprof[0]/1000.0
|
||||
<< " H2D " << tprof[1]/1000.0
|
||||
<< " gemm+reduce "<< tprof[2]/1000.0
|
||||
<< " D2H " << tprof[3]/1000.0
|
||||
<< " unpack " << (t6-t5)/1000.0
|
||||
<< " ms" << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,238 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./lib/algorithms/GeneralCoarsenedMatrix.h
|
||||
|
||||
Copyright (C) 2015
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Geometry class in cartesian case
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
class Geometry {
|
||||
public:
|
||||
int npoint;
|
||||
int base;
|
||||
std::vector<int> directions ;
|
||||
std::vector<int> displacements;
|
||||
std::vector<int> points_dagger;
|
||||
|
||||
Geometry(int _d) {
|
||||
|
||||
base = (_d==5) ? 1:0;
|
||||
|
||||
// make coarse grid stencil for 4d , not 5d
|
||||
if ( _d==5 ) _d=4;
|
||||
|
||||
npoint = 2*_d+1;
|
||||
directions.resize(npoint);
|
||||
displacements.resize(npoint);
|
||||
points_dagger.resize(npoint);
|
||||
for(int d=0;d<_d;d++){
|
||||
directions[d ] = d+base;
|
||||
directions[d+_d] = d+base;
|
||||
displacements[d ] = +1;
|
||||
displacements[d+_d]= -1;
|
||||
points_dagger[d ] = d+_d;
|
||||
points_dagger[d+_d] = d;
|
||||
}
|
||||
directions [2*_d]=0;
|
||||
displacements[2*_d]=0;
|
||||
points_dagger[2*_d]=2*_d;
|
||||
}
|
||||
|
||||
int point(int dir, int disp) {
|
||||
GRID_ASSERT(disp == -1 || disp == 0 || disp == 1);
|
||||
GRID_ASSERT(base+0 <= dir && dir < base+4);
|
||||
|
||||
// directions faster index = new indexing
|
||||
// 4d (base = 0):
|
||||
// point 0 1 2 3 4 5 6 7 8
|
||||
// dir 0 1 2 3 0 1 2 3 0
|
||||
// disp +1 +1 +1 +1 -1 -1 -1 -1 0
|
||||
// 5d (base = 1):
|
||||
// point 0 1 2 3 4 5 6 7 8
|
||||
// dir 1 2 3 4 1 2 3 4 0
|
||||
// disp +1 +1 +1 +1 -1 -1 -1 -1 0
|
||||
|
||||
// displacements faster index = old indexing
|
||||
// 4d (base = 0):
|
||||
// point 0 1 2 3 4 5 6 7 8
|
||||
// dir 0 0 1 1 2 2 3 3 0
|
||||
// disp +1 -1 +1 -1 +1 -1 +1 -1 0
|
||||
// 5d (base = 1):
|
||||
// point 0 1 2 3 4 5 6 7 8
|
||||
// dir 1 1 2 2 3 3 4 4 0
|
||||
// disp +1 -1 +1 -1 +1 -1 +1 -1 0
|
||||
|
||||
if(dir == 0 and disp == 0)
|
||||
return 8;
|
||||
else // New indexing
|
||||
return (1 - disp) / 2 * 4 + dir - base;
|
||||
// else // Old indexing
|
||||
// return (4 * (dir - base) + 1 - disp) / 2;
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// Less local equivalent of Geometry class in cartesian case
|
||||
/////////////////////////////////////////////////////////////////
|
||||
class NonLocalStencilGeometry {
|
||||
public:
|
||||
// int depth;
|
||||
int skip;
|
||||
int hops;
|
||||
int npoint;
|
||||
std::vector<Coordinate> shifts;
|
||||
Coordinate stencil_size;
|
||||
Coordinate stencil_lo;
|
||||
Coordinate stencil_hi;
|
||||
GridCartesian *grid;
|
||||
GridCartesian *Grid() {return grid;};
|
||||
int Depth(void){return 1;}; // Ghost zone depth
|
||||
int Hops(void){return hops;}; // # of hops=> level of corner fill in in stencil
|
||||
int DimSkip(void){return skip;};
|
||||
|
||||
virtual ~NonLocalStencilGeometry() {};
|
||||
|
||||
int Reverse(int point)
|
||||
{
|
||||
int Nd = Grid()->Nd();
|
||||
Coordinate shft = shifts[point];
|
||||
Coordinate rev(Nd);
|
||||
for(int mu=0;mu<Nd;mu++) rev[mu]= -shft[mu];
|
||||
for(int p=0;p<npoint;p++){
|
||||
if(rev==shifts[p]){
|
||||
return p;
|
||||
}
|
||||
}
|
||||
GRID_ASSERT(0);
|
||||
return -1;
|
||||
}
|
||||
void BuildShifts(void)
|
||||
{
|
||||
this->shifts.resize(0);
|
||||
int Nd = this->grid->Nd();
|
||||
|
||||
int dd = this->DimSkip();
|
||||
for(int s0=this->stencil_lo[dd+0];s0<=this->stencil_hi[dd+0];s0++){
|
||||
for(int s1=this->stencil_lo[dd+1];s1<=this->stencil_hi[dd+1];s1++){
|
||||
for(int s2=this->stencil_lo[dd+2];s2<=this->stencil_hi[dd+2];s2++){
|
||||
for(int s3=this->stencil_lo[dd+3];s3<=this->stencil_hi[dd+3];s3++){
|
||||
Coordinate sft(Nd,0);
|
||||
sft[dd+0] = s0;
|
||||
sft[dd+1] = s1;
|
||||
sft[dd+2] = s2;
|
||||
sft[dd+3] = s3;
|
||||
int nhops = abs(s0)+abs(s1)+abs(s2)+abs(s3);
|
||||
if(nhops<=this->hops) this->shifts.push_back(sft);
|
||||
}}}}
|
||||
this->npoint = this->shifts.size();
|
||||
std::cout << GridLogMessage << "NonLocalStencilGeometry has "<< this->npoint << " terms in stencil "<<std::endl;
|
||||
}
|
||||
|
||||
NonLocalStencilGeometry(GridCartesian *_coarse_grid,int _hops,int _skip) : grid(_coarse_grid), hops(_hops), skip(_skip)
|
||||
{
|
||||
Coordinate latt = grid->GlobalDimensions();
|
||||
stencil_size.resize(grid->Nd());
|
||||
stencil_lo.resize(grid->Nd());
|
||||
stencil_hi.resize(grid->Nd());
|
||||
for(int d=0;d<grid->Nd();d++){
|
||||
if ( latt[d] == 1 ) {
|
||||
stencil_lo[d] = 0;
|
||||
stencil_hi[d] = 0;
|
||||
stencil_size[d]= 1;
|
||||
} else if ( latt[d] == 2 ) {
|
||||
stencil_lo[d] = -1;
|
||||
stencil_hi[d] = 0;
|
||||
stencil_size[d]= 2;
|
||||
} else if ( latt[d] > 2 ) {
|
||||
stencil_lo[d] = -1;
|
||||
stencil_hi[d] = 1;
|
||||
stencil_size[d]= 3;
|
||||
}
|
||||
}
|
||||
this->BuildShifts();
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
// Need to worry about red-black now
|
||||
class NonLocalStencilGeometry4D : public NonLocalStencilGeometry {
|
||||
public:
|
||||
virtual int DerivedDimSkip(void) { return 0;};
|
||||
NonLocalStencilGeometry4D(GridCartesian *Coarse,int _hops) : NonLocalStencilGeometry(Coarse,_hops,0) { };
|
||||
virtual ~NonLocalStencilGeometry4D() {};
|
||||
};
|
||||
class NonLocalStencilGeometry5D : public NonLocalStencilGeometry {
|
||||
public:
|
||||
virtual int DerivedDimSkip(void) { return 1; };
|
||||
NonLocalStencilGeometry5D(GridCartesian *Coarse,int _hops) : NonLocalStencilGeometry(Coarse,_hops,1) { };
|
||||
virtual ~NonLocalStencilGeometry5D() {};
|
||||
};
|
||||
/*
|
||||
* Bunch of different options classes
|
||||
*/
|
||||
class NextToNextToNextToNearestStencilGeometry4D : public NonLocalStencilGeometry4D {
|
||||
public:
|
||||
NextToNextToNextToNearestStencilGeometry4D(GridCartesian *Coarse) : NonLocalStencilGeometry4D(Coarse,4)
|
||||
{
|
||||
};
|
||||
};
|
||||
class NextToNextToNextToNearestStencilGeometry5D : public NonLocalStencilGeometry5D {
|
||||
public:
|
||||
NextToNextToNextToNearestStencilGeometry5D(GridCartesian *Coarse) : NonLocalStencilGeometry5D(Coarse,4)
|
||||
{
|
||||
};
|
||||
};
|
||||
class NextToNearestStencilGeometry4D : public NonLocalStencilGeometry4D {
|
||||
public:
|
||||
NextToNearestStencilGeometry4D(GridCartesian *Coarse) : NonLocalStencilGeometry4D(Coarse,2)
|
||||
{
|
||||
};
|
||||
};
|
||||
class NextToNearestStencilGeometry5D : public NonLocalStencilGeometry5D {
|
||||
public:
|
||||
NextToNearestStencilGeometry5D(GridCartesian *Coarse) : NonLocalStencilGeometry5D(Coarse,2)
|
||||
{
|
||||
};
|
||||
};
|
||||
class NearestStencilGeometry4D : public NonLocalStencilGeometry4D {
|
||||
public:
|
||||
NearestStencilGeometry4D(GridCartesian *Coarse) : NonLocalStencilGeometry4D(Coarse,1)
|
||||
{
|
||||
};
|
||||
};
|
||||
class NearestStencilGeometry5D : public NonLocalStencilGeometry5D {
|
||||
public:
|
||||
NearestStencilGeometry5D(GridCartesian *Coarse) : NonLocalStencilGeometry5D(Coarse,1)
|
||||
{
|
||||
};
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,632 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./Grid/algorithms/multigrid/HDCGMultiGrid.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution
|
||||
directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
#include <Grid/algorithms/iterative/ImplicitlyRestartedBlockLanczos.h>
|
||||
#include <Grid/algorithms/iterative/ImplicitlyRestartedBlockLanczosCoarse.h>
|
||||
#include <Grid/algorithms/multigrid/PVdagMMultiGrid.h>
|
||||
#include <Grid/algorithms/multigrid/HDCGMultiGridParams.h>
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// The two-level mrhs HDCG (arXiv:1402.2585, mrhs form arXiv:2409.03904) as
|
||||
// objects, the shape of PVdagMMultiGrid.h:
|
||||
//
|
||||
// MGCoarseGrids / MGFineGridsF shared with the PVdagM chain
|
||||
// HDCGCoarsening everything that is a function of the gauge
|
||||
// field: the raw near-null basis (and its
|
||||
// refinement), the transfer operators, the
|
||||
// Galerkin coarse operator (Hermitian),
|
||||
// the coarse eigenvectors for deflation
|
||||
// HDCGSolver the solve chain on a borrowed coarsening:
|
||||
// smoother, deflated coarse solve, ADEF-2
|
||||
// preconditioner (fp64 or fp32 behind the
|
||||
// seam), outer mrhs fPcg / BlockCGrQ
|
||||
//
|
||||
// The Hermitian operator is the Schur-preconditioned M^dag M on one
|
||||
// checkerboard; the near-null basis and the fine fields live on the
|
||||
// checkerboarded grid. The coarse space is unvectorised and ALWAYS the D+1
|
||||
// multiRHS field; Nrhs 1 is the extent-1 case. Grids outlive the
|
||||
// coarsening outlives the solver.
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
template<class Fobj,class CComplex,int nbasis>
|
||||
class HDCGCoarsening {
|
||||
public:
|
||||
typedef Lattice<Fobj> FineField;
|
||||
typedef MultiGeneralCoarsenedOperator<Fobj,CComplex,nbasis> CoarseOperator;
|
||||
typedef typename CoarseOperator::CoarseVector CoarseVector;
|
||||
typedef typename GridTypeMapper<Fobj>::SinglePrecision FobjF;
|
||||
typedef Lattice<FobjF> FineFieldF;
|
||||
|
||||
MGCoarseGrids &Grids; // borrowed
|
||||
MGFineGridsF &GridsF; // borrowed
|
||||
GridBase *FrbGrid; // borrowed: the checkerboarded fine grid
|
||||
int cb;
|
||||
HDCGMultiGridParams Params;
|
||||
NextToNextToNextToNearestStencilGeometry5D geom; // the coarse stencil of the Schur operator
|
||||
CoarseOperator CoarseOp;
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// ONE fine transfer operator. Its STORE follows the coarse sector --
|
||||
// the coarse space is what it feeds -- while its import and export
|
||||
// accept EITHER fine precision, because they are already a layout
|
||||
// transformation and a scalar conversion inside one costs nothing.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
static const bool CoarseIsSingle =
|
||||
( sizeof(typename GridTypeMapper<CComplex>::scalar_type) == sizeof(ComplexF) );
|
||||
typedef typename std::conditional<CoarseIsSingle,FineFieldF,FineField>::type ProjectorField;
|
||||
typedef MultiRHSBlockProject<ProjectorField> ProjectorL1_t;
|
||||
|
||||
ProjectorL1_t MrhsProjectorL1;
|
||||
std::vector<FineField> rawNull; // RAW fine near-null basis
|
||||
std::vector<CoarseVector> evec; // coarse eigenvectors, on Coarse5d
|
||||
std::vector<RealD> eval;
|
||||
MultiRHSDeflation<CoarseVector> Deflator; // nev==0 until CoarseLanczos
|
||||
GridCartesian *CMrhs; // transient solve grid, owned
|
||||
int nrhs;
|
||||
|
||||
HDCGCoarsening(MGCoarseGrids &_Grids, MGFineGridsF &_GridsF, GridBase *_FrbGrid, int _cb,
|
||||
const HDCGMultiGridParams &P)
|
||||
: Grids(_Grids), GridsF(_GridsF), FrbGrid(_FrbGrid), cb(_cb), Params(P),
|
||||
geom(_Grids.Coarse5d),
|
||||
CoarseOp(geom,_Grids.Coarse5d),
|
||||
CMrhs(nullptr),
|
||||
nrhs(-1)
|
||||
{
|
||||
Deflator.Deallocate();
|
||||
// The coarse stencil is a max-norm-1 box (Geometry.h) and the Schur
|
||||
// operator M^dag M reaches four fine hops, so a block shorter than 4
|
||||
// leaves couplings outside the stencil: the Fourier extraction then
|
||||
// folds them into the retained points and the Galerkin certificate
|
||||
// reads it. Warn here; the certificate is the guard.
|
||||
for(int d=0;d<4;d++){
|
||||
if ( Params.Setup.Block1[d] < 4 )
|
||||
std::cout << GridLogWarning << "HDCGCoarsening: Block1[" << d << "] = " << Params.Setup.Block1[d]
|
||||
<< " < 4: the Schur operator's range exceeds the coarse stencil; "
|
||||
<< "expect a Galerkin defect" << std::endl;
|
||||
}
|
||||
};
|
||||
~HDCGCoarsening()
|
||||
{
|
||||
CoarseOp.ReleaseGrid(); // borrowers let go before their grids die
|
||||
if ( CMrhs ) delete CMrhs;
|
||||
}
|
||||
|
||||
int FileExists(const std::string &fname)
|
||||
{
|
||||
uint64_t exists=0;
|
||||
if ( fname.length() ) {
|
||||
if ( Grids.FGrid->IsBoss() ){ std::ifstream f(fname); exists=f.good()?1:0; }
|
||||
Grids.FGrid->GlobalSum(exists);
|
||||
}
|
||||
return (int)exists;
|
||||
}
|
||||
FineField FineNoise(GridParallelRNG &RNG)
|
||||
{
|
||||
FineField v(FrbGrid); v.Checkerboard()=cb; gaussian(RNG,v);
|
||||
return v;
|
||||
}
|
||||
// Chebyshev upper bound: the parameter, or 1.1 x a power-method estimate
|
||||
RealD UpperBound(RealD param, LinearOperatorBase<FineField> &HermOp, GridParallelRNG &RNG)
|
||||
{
|
||||
if ( param > 0.0 ) return param;
|
||||
FineField src = FineNoise(RNG);
|
||||
PowerMethod<FineField> PM;
|
||||
RealD lam = PM(HermOp,src);
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: fine power method " << lam
|
||||
<< ", Chebyshev upper bound " << 1.1*lam << std::endl;
|
||||
return 1.1*lam;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// The RAW basis: a refined basis on disk wins, then a raw basis on
|
||||
// disk, else built by the chosen method (and saved if named). CG
|
||||
// refinement happens here; HDCG refinement needs a solver, see
|
||||
// HDCGRefineSubspace below. The Aggregation is scaffolding for the
|
||||
// Chebyshev filters only: they run entirely on the fine grid.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void GetSubspace(GridParallelRNG &RNG, LinearOperatorBase<FineField> &HermOp)
|
||||
{
|
||||
const HDCGSubspaceParams &S = Params.Subspace;
|
||||
rawNull.clear();
|
||||
rawNull.reserve(nbasis);
|
||||
for(int k=0;k<nbasis;k++){ rawNull.push_back(FineField(FrbGrid)); rawNull[k].Checkerboard()=cb; }
|
||||
|
||||
if ( FileExists(S.RefinedSubspaceFile) ) {
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: loading REFINED subspace " << S.RefinedSubspaceFile << std::endl;
|
||||
loadSubspace(rawNull, S.RefinedSubspaceFile, S.FileControl);
|
||||
return;
|
||||
}
|
||||
if ( FileExists(Params.Setup.SubspaceFile) ) {
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: loading subspace " << Params.Setup.SubspaceFile << std::endl;
|
||||
loadSubspace(rawNull, Params.Setup.SubspaceFile, S.FileControl);
|
||||
} else {
|
||||
Aggregation<Fobj,CComplex,nbasis> Agg(Grids.Coarse5d,FrbGrid,cb);
|
||||
if ( S.Method == HDCGSubspaceMethod::ChebyshevNew ) {
|
||||
RealD hi = UpperBound(S.ChebyHi,HermOp,RNG);
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: CreateSubspaceChebyshevNew hi " << hi << std::endl;
|
||||
Agg.CreateSubspaceChebyshevNew(RNG,HermOp,hi);
|
||||
} else if ( S.Method == HDCGSubspaceMethod::Chebyshev ) {
|
||||
RealD hi = UpperBound(S.ChebyHi,HermOp,RNG);
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: CreateSubspaceChebyshev hi " << hi
|
||||
<< " lo " << S.ChebyLo << " order " << S.ChebyOrder << std::endl;
|
||||
Agg.CreateSubspaceChebyshev(RNG,HermOp,nbasis,hi,S.ChebyLo,S.ChebyOrder);
|
||||
} else {
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: subspace from fine eigenvectors " << S.FineEvecFile
|
||||
<< " summed " << S.FineEvecSample << " per vector" << std::endl;
|
||||
LoadFineEvecSum(Agg.subspace,S.FineEvecFile,S.FineEvecSample,S.FileControl);
|
||||
}
|
||||
for(int k=0;k<nbasis;k++) rawNull[k]=Agg.subspace[k];
|
||||
if ( Params.Setup.SubspaceFile.length() )
|
||||
saveSubspace(rawNull, Params.Setup.SubspaceFile, S.FileControl);
|
||||
}
|
||||
if ( S.Refine == HDCGRefineMethod::CG ) {
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: CG refinement shift " << S.RefineShift
|
||||
<< " tol " << S.RefineTol << " maxit " << S.RefineMaxIt << std::endl;
|
||||
Aggregation<Fobj,CComplex,nbasis> Agg(Grids.Coarse5d,FrbGrid,cb);
|
||||
for(int k=0;k<nbasis;k++) Agg.subspace[k]=rawNull[k];
|
||||
Agg.RefineSubspace(HermOp,S.RefineShift,S.RefineTol,S.RefineMaxIt);
|
||||
for(int k=0;k<nbasis;k++) rawNull[k]=Agg.subspace[k];
|
||||
if ( S.RefinedSubspaceFile.length() )
|
||||
saveSubspace(rawNull, S.RefinedSubspaceFile, S.FileControl);
|
||||
}
|
||||
}
|
||||
// Basis vector b = precisionChange of the sum of `sample` consecutive fp32 records
|
||||
void LoadFineEvecSum(std::vector<FineField> &sub, const std::string &file, int sample, int control)
|
||||
{
|
||||
#ifdef HAVE_LIME
|
||||
emptyUserRecord record;
|
||||
FineFieldF tmp(GridsF.FrbGridF), sum(GridsF.FrbGridF);
|
||||
tmp.Checkerboard()=cb; sum.Checkerboard()=cb;
|
||||
ScidacReader RD;
|
||||
RD.open(file);
|
||||
for(int b=0;b<nbasis;b++){
|
||||
sum = Zero();
|
||||
for(int n=0;n<sample;n++){
|
||||
RD.readScidacFieldRecord(tmp,record,control);
|
||||
sum = sum + tmp;
|
||||
}
|
||||
precisionChange(sub[b],sum);
|
||||
}
|
||||
RD.close();
|
||||
#else
|
||||
GRID_ASSERT(0);
|
||||
#endif
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// HDCG refinement of the raw basis (the paper's loop): blocks of
|
||||
// nrhs vectors, normalised, solved with the refining solver, and
|
||||
// renormalised. The solver was built on the SHIFTED operator and
|
||||
// the coarsening of it; the caller re-coarsens afterwards.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void RefineSubspaceHDCG(LinearOperatorBase<FineField> &HermOp, TwoLevelCGmrhs<FineField> &Refiner, int nr)
|
||||
{
|
||||
std::vector<FineField> src(nr,FrbGrid), res(nr,FrbGrid);
|
||||
FineField tmp(FrbGrid); tmp.Checkerboard()=cb;
|
||||
for(int r=0;r<nr;r++){ src[r].Checkerboard()=cb; res[r].Checkerboard()=cb; }
|
||||
for(int b=0;b<nbasis;b+=nr){
|
||||
int nb = std::min(nbasis-b,nr);
|
||||
for(int r=0;r<nb;r++){
|
||||
RealD scale = std::pow(norm2(rawNull[b+r]),-0.5);
|
||||
src[r] = rawNull[b+r]*scale;
|
||||
}
|
||||
for(int r=nb;r<nr;r++) src[r] = src[0]; // pad the block; results discarded
|
||||
for(int r=0;r<nr;r++) res[r] = Zero();
|
||||
HermOp.HermOp(src[0],tmp);
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: refine block " << b << " before <n|A|n> " << norm2(tmp) << std::endl;
|
||||
Refiner(src,res);
|
||||
for(int r=0;r<nb;r++){
|
||||
RealD scale = std::pow(norm2(res[r]),-0.5);
|
||||
rawNull[b+r] = res[r]*scale;
|
||||
}
|
||||
HermOp.HermOp(rawNull[b],tmp);
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: refine block " << b << " after <n|A|n> " << norm2(tmp) << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// The Galerkin coarse operator and transfer operators from the RAW
|
||||
// basis. CoarsenOperator block-orthonormalises its input in place,
|
||||
// so a working copy is taken and the RAW vectors survive. The
|
||||
// operator handed in must apply the Hermitian operator through Op
|
||||
// (HermOpAdaptor).
|
||||
//
|
||||
// The RAW basis has done its work once this returns, so it is freed --
|
||||
// it is nbasis fine vectors, the single largest resident block of the
|
||||
// setup. retain_basis keeps it, for the two callers that coarsen more
|
||||
// than once: the HDCG subspace refinement below, and a fixed-basis
|
||||
// rebuild on a changed gauge field (HMC). Setup.RetainSubspace keeps
|
||||
// it for the whole life of the object.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void Coarsen(LinearOperatorBase<FineField> &HermOp, int retain_basis=0)
|
||||
{
|
||||
GRID_ASSERT( rawNull.size() == nbasis );
|
||||
std::vector<FineField> sub(rawNull);
|
||||
|
||||
CoarseOp.SetGrid(Grids.CoarseBatch);
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: CoarsenOperator, batch " << Grids.batch << std::endl;
|
||||
// CoarsenOperator orthonormalises sub in place and imports it into OUR
|
||||
// transfer operator, which it then uses. One basis store, imported once.
|
||||
// The projector's grid carries the STORE's SIMD layout: the fp32 fine
|
||||
// red-black grid when the coarse sector is fp32.
|
||||
MrhsProjectorL1.Allocate(nbasis,
|
||||
CoarseIsSingle ? (GridBase *)GridsF.FrbGridF : (GridBase *)FrbGrid,
|
||||
Grids.Coarse5d);
|
||||
CoarseOp.CoarsenOperator(HermOp,sub,Grids.Coarse5d,Grids.batch,MrhsProjectorL1);
|
||||
if ( !retain_basis && !Params.Setup.RetainSubspace ) DiscardBasis();
|
||||
MrhsProjectorL1.ReleaseScratch();
|
||||
nrhs = -1; // the batch grid is set; SetNrhs retargets
|
||||
ReportDeviceFootprint("after Coarsen");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Retarget the coarse operator to Nrhs: the D+1 grid is owned here
|
||||
// and every borrower re-captures it after this call.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void SetNrhs(int nr)
|
||||
{
|
||||
if ( nr == nrhs ) return;
|
||||
CoarseOp.ReleaseGrid();
|
||||
if ( CMrhs ) { delete CMrhs; CMrhs=nullptr; }
|
||||
Coordinate cml({nr,1,Grids.clatt[0],Grids.clatt[1],Grids.clatt[2],Grids.clatt[3]});
|
||||
CMrhs = new GridCartesian(cml,Grids.cmsimd,Grids.cmmpi);
|
||||
CoarseOp.SetGrid(CMrhs);
|
||||
nrhs = nr;
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: coarse operator at Nrhs " << nr << std::endl;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Coarse eigenvectors for the deflated coarse solve. Block Lanczos
|
||||
// runs at the solve's Nrhs on the D+1 operator; plain Lanczos at
|
||||
// Nrhs 1 (the extent-1 D+1 grid), its vectors then sliced to the D
|
||||
// grid so both feed the deflator the same way. Loaded from EvecFile
|
||||
// when present, saved there when named.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void CoarseLanczos(int nr)
|
||||
{
|
||||
const HDCGLanczosParams &L = Params.Lanczos;
|
||||
evec.clear(); eval.clear();
|
||||
Deflator.Deallocate();
|
||||
if ( L.Method == HDCGLanczosMethod::None ) {
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: no coarse deflation" << std::endl;
|
||||
return;
|
||||
}
|
||||
if ( FileExists(L.EvecFile) ) {
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: loading coarse eigenpairs " << L.EvecFile << std::endl;
|
||||
loadEigenpairs(evec,eval,L.EvecFile,Grids.Coarse5d,Params.Subspace.FileControl);
|
||||
} else {
|
||||
int Nconv=0;
|
||||
SetNrhs( L.Method == HDCGLanczosMethod::Block ? nr : 1 );
|
||||
HermitianLinearOperator<CoarseOperator,CoarseVector> HermOpC(CoarseOp);
|
||||
GridParallelRNG cRNG(CMrhs); cRNG.SeedFixedIntegers(std::vector<int>({31,32,33,34}));
|
||||
RealD hi = L.ChebyHi;
|
||||
if ( hi <= 0.0 ) {
|
||||
CoarseVector s(CMrhs); random(cRNG,s);
|
||||
PowerMethod<CoarseVector> PM;
|
||||
hi = 1.1*PM(HermOpC,s);
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: coarse Chebyshev upper bound " << hi << std::endl;
|
||||
}
|
||||
Chebyshev<CoarseVector> Cheby(L.ChebyLo,hi,L.ChebyOrder);
|
||||
|
||||
if ( L.Method == HDCGLanczosMethod::Block ) {
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: block Lanczos, Nrhs " << nr
|
||||
<< " Nstop " << L.Nstop << " Nk " << L.Nk << " Nm " << L.Nm << std::endl;
|
||||
ImplicitlyRestartedBlockLanczosCoarse<CoarseVector>
|
||||
IRL(HermOpC,Grids.Coarse5d,CMrhs,nr,Cheby,L.Nstop,1,nr,L.Nk,L.Nm,L.Tol,L.MaxIt);
|
||||
evec.resize(L.Nm,Grids.Coarse5d);
|
||||
eval.resize(L.Nm);
|
||||
std::vector<CoarseVector> src(nr,Grids.Coarse5d);
|
||||
GridParallelRNG dRNG(Grids.Coarse5d); dRNG.SeedFixedIntegers(std::vector<int>({41,42,43,44}));
|
||||
for(int r=0;r<nr;r++) random(dRNG,src[r]);
|
||||
IRL.calc(eval,evec,src,Nconv,LanczosType::irbl);
|
||||
evec.resize(Nconv,Grids.Coarse5d); // only the converged pairs
|
||||
eval.resize(Nconv);
|
||||
} else {
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: plain Lanczos at Nrhs 1, Nstop " << L.Nstop
|
||||
<< " Nk " << L.Nk << " Nm " << L.Nm << std::endl;
|
||||
PlainHermOp<CoarseVector> Op(HermOpC);
|
||||
FunctionHermOp<CoarseVector> OpCheby(Cheby,HermOpC);
|
||||
ImplicitlyRestartedLanczos<CoarseVector> IRL(OpCheby,Op,L.Nstop,L.Nk,L.Nm,L.Tol,L.MaxIt);
|
||||
std::vector<CoarseVector> evec1(L.Nm,CMrhs);
|
||||
std::vector<RealD> eval1(L.Nm);
|
||||
CoarseVector src(CMrhs); random(cRNG,src);
|
||||
IRL.calc(eval1,evec1,src,Nconv);
|
||||
evec.resize(Nconv,Grids.Coarse5d);
|
||||
eval.resize(Nconv);
|
||||
for(int e=0;e<Nconv;e++){ ExtractSliceFast(evec[e],evec1[e],0,0); eval[e]=eval1[e]; }
|
||||
}
|
||||
// The Lanczos routines set their outputs only on reaching Nstop;
|
||||
// a partial result carries no usable eigenpairs.
|
||||
if ( Nconv < L.Nstop ) {
|
||||
std::cout << GridLogWarning << "HDCGCoarsening: coarse Lanczos converged " << Nconv << " < Nstop " << L.Nstop
|
||||
<< " modes (Chebyshev window [" << L.ChebyLo << "," << hi << "] must hold at least Nstop eigenvalues"
|
||||
<< " and Nm must resolve them); coarse solve undeflated" << std::endl;
|
||||
evec.clear(); eval.clear();
|
||||
return;
|
||||
}
|
||||
if ( L.EvecFile.length() ) saveEigenpairs(evec,eval,L.EvecFile,Params.Subspace.FileControl);
|
||||
}
|
||||
const int nev = evec.size();
|
||||
Deflator.ImportEigenBasis(evec,eval);
|
||||
// The Lattice-layout copies have been imported into the BLAS store and
|
||||
// saved if they were going to be; the solve reads only the BLAS store.
|
||||
evec.clear(); evec.shrink_to_fit();
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: " << nev << " coarse eigenvectors imported for deflation"
|
||||
<< (nev ? " (lowest "+std::to_string(eval[0])+", highest "+std::to_string(eval[nev-1])+")" : "")
|
||||
<< std::endl;
|
||||
ReportDeviceFootprint("after CoarseLanczos");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Certificates at Nrhs 1 through the solve's own transfer operators:
|
||||
// Galerkin ||A_c x - P^dag A P x|| / ||A_c x|| (the rounding level of
|
||||
// the coarsening: fp64 coarse ~1e-13, fp32 ~1e-6), and Hermiticity
|
||||
// |<x,A_c y> - <A_c x,y>| / |<x,A_c y>|, which CG relies on.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
RealD CertifyCoarsening(LinearOperatorBase<FineField> &HermOp)
|
||||
{
|
||||
SetNrhs(1);
|
||||
CoarseVector xc(CMrhs), yc(CMrhs), Acx(CMrhs), Acy(CMrhs), PtAPx(CMrhs);
|
||||
GridParallelRNG cRNG(CMrhs); cRNG.SeedFixedIntegers(std::vector<int>({21,22,23,24}));
|
||||
random(cRNG,xc);
|
||||
random(cRNG,yc);
|
||||
|
||||
CoarseOp.M(xc,Acx); // A_c x
|
||||
CoarseOp.M(yc,Acy); // A_c y
|
||||
|
||||
std::vector<FineField> Px(1,FrbGrid), APx(1,FrbGrid);
|
||||
Px[0].Checkerboard()=cb; APx[0].Checkerboard()=cb;
|
||||
MrhsProjectorL1.blockPromote(Px,xc); // P x
|
||||
HermOp.HermOp(Px[0],APx[0]); // A P x
|
||||
MrhsProjectorL1.blockProject(APx,PtAPx); // P^dag A P x
|
||||
|
||||
CoarseVector d(CMrhs); d = Acx - PtAPx;
|
||||
RealD rel = std::sqrt(norm2(d)/norm2(Acx));
|
||||
ComplexD xAy = TensorRemove(innerProduct(xc,Acy));
|
||||
ComplexD Axy = TensorRemove(innerProduct(Acx,yc));
|
||||
RealD herm = abs(xAy-Axy)/abs(xAy);
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: GALERKIN CERTIFICATE ||A_c x - P^dag A P x||/||A_c x|| = " << rel << std::endl;
|
||||
std::cout << GridLogMessage << "HDCGCoarsening: HERMITICITY CERTIFICATE |<x,A_c y> - <A_c x,y>|/|<x,A_c y>| = " << herm << std::endl;
|
||||
return rel;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// The device memory this coarsening holds that the memory manager
|
||||
// CANNOT evict: BLAS stores, which are plain device allocations.
|
||||
// Lattice fields are omitted on purpose -- they are evictable, so they
|
||||
// cost eviction traffic, not failure. What is reported is what has to
|
||||
// fit alongside the manager's cache, the comms buffers and the shm
|
||||
// segment, which is the budget an out-of-memory run actually breaks.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
void ReportDeviceFootprint(const std::string &when)
|
||||
{
|
||||
uint64_t p1 = MrhsProjectorL1.DeviceBytes();
|
||||
uint64_t o1 = CoarseOp.DeviceBytes();
|
||||
uint64_t df = Deflator.DeviceBytes();
|
||||
const double G = 1024.*1024.*1024.;
|
||||
std::cout << GridLogMessage << "HDCGCoarsening (" << when << "): resident device memory/rank "
|
||||
<< (p1+o1+df)/G << " GB = transfer L1 " << p1/G
|
||||
<< " + operator L1 " << o1/G
|
||||
<< " + deflation " << df/G << " GB" << std::endl;
|
||||
std::cout << GridLogMessage << "HDCGCoarsening (" << when << "): not evictable; must fit alongside the "
|
||||
<< MemoryManager::DeviceMaxBytes/G
|
||||
<< " GB manager cache, the comms buffers and the shm segment" << std::endl;
|
||||
}
|
||||
|
||||
void DiscardBasis(void)
|
||||
{
|
||||
rawNull.clear(); rawNull.shrink_to_fit();
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// The solve chain on a borrowed coarsening. Both fine precisions are
|
||||
// built; the outer picks the fp32 preconditioner through the seam when
|
||||
// Setup.FinePrecision is fp32. OperatorShift is 0 in production; the
|
||||
// HDCG-refinement solver runs on the shifted operator.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
template<class Matrix,class MatrixF,class Coarsening>
|
||||
class HDCGSolver {
|
||||
public:
|
||||
typedef typename Coarsening::FineField FineField;
|
||||
typedef typename Coarsening::FineFieldF FineFieldF;
|
||||
typedef typename Coarsening::CoarseOperator CoarseOperator;
|
||||
typedef typename Coarsening::CoarseVector CoarseVector;
|
||||
|
||||
Coarsening &C;
|
||||
HDCGMultiGridParams Params;
|
||||
int nrhs;
|
||||
int _regrid; // FIRST member-like init: SetNrhs before members capture grids
|
||||
RealD OperatorShift;
|
||||
|
||||
// fp64 fine chain
|
||||
SchurDiagMooeeOperator<Matrix,FineField> HermOpEO;
|
||||
ShiftedHermOpLinearOperator<FineField> HermOpShifted;
|
||||
HermOpAdaptor<FineField> FineOp; // Op = HermOp: the operator of the solve
|
||||
ShiftedHermOpLinearOperator<FineField> SmootherOp;
|
||||
CGSmoother<FineField> SmootherCG;
|
||||
ChebyshevSmoother<FineField> SmootherCheby;
|
||||
// coarse
|
||||
HermitianLinearOperator<CoarseOperator,CoarseVector> LinOpC;
|
||||
ConjugateGradient<CoarseVector> CoarseCG;
|
||||
DoNothingGuesser<CoarseVector> NoGuess;
|
||||
HPDSolver<CoarseVector> CoarseDeflatedCG;
|
||||
ChebyshevInverter<CoarseVector> CoarseCheby;
|
||||
MrhsADEF2Preconditioner<FineField,CoarseVector,typename Coarsening::ProjectorL1_t> ADEF2;
|
||||
// fp32 fine chain, behind the seam; the coarse solve is shared
|
||||
SchurDiagMooeeOperator<MatrixF,FineFieldF> HermOpEOF;
|
||||
ShiftedHermOpLinearOperator<FineFieldF> HermOpShiftedF;
|
||||
HermOpAdaptor<FineFieldF> FineOpF;
|
||||
ShiftedHermOpLinearOperator<FineFieldF> SmootherOpF;
|
||||
CGSmoother<FineFieldF> SmootherCGF;
|
||||
ChebyshevSmoother<FineFieldF> SmootherChebyF;
|
||||
MrhsADEF2Preconditioner<FineFieldF,CoarseVector,typename Coarsening::ProjectorL1_t> ADEF2F;
|
||||
MrhsMixedPrecPreconditioner<FineField,FineFieldF> PreconSeam;
|
||||
TwoLevelCGmrhs<FineField> Outer;
|
||||
|
||||
LinearFunction<FineField> &PickSmoother(void) {
|
||||
return (Params.Smoother.Type==HDCGSmootherType::CG)
|
||||
? static_cast<LinearFunction<FineField>&>(SmootherCG)
|
||||
: static_cast<LinearFunction<FineField>&>(SmootherCheby);
|
||||
}
|
||||
LinearFunction<FineFieldF> &PickSmootherF(void) {
|
||||
return (Params.Smoother.Type==HDCGSmootherType::CG)
|
||||
? static_cast<LinearFunction<FineFieldF>&>(SmootherCGF)
|
||||
: static_cast<LinearFunction<FineFieldF>&>(SmootherChebyF);
|
||||
}
|
||||
LinearFunction<CoarseVector> &PickCoarseSolve(void) {
|
||||
return (Params.CoarseSolver.Method==HDCGCoarseMethod::DeflatedCG)
|
||||
? static_cast<LinearFunction<CoarseVector>&>(CoarseDeflatedCG)
|
||||
: static_cast<LinearFunction<CoarseVector>&>(CoarseCheby);
|
||||
}
|
||||
template<class F, class Op, class ShiftedOp>
|
||||
static LinearOperatorBase<F> &Unshifted(RealD shift, Op &op, ShiftedOp &shifted) {
|
||||
return (shift==0.0) ? static_cast<LinearOperatorBase<F>&>(op) : static_cast<LinearOperatorBase<F>&>(shifted);
|
||||
}
|
||||
|
||||
HDCGSolver(Matrix &Ddwf, MatrixF &DdwfF, Coarsening &_C, const HDCGMultiGridParams &P, int nr, RealD shift=0.0)
|
||||
: C(_C), Params(P), nrhs(nr),
|
||||
_regrid((C.SetNrhs(nr),0)),
|
||||
OperatorShift(shift),
|
||||
HermOpEO(Ddwf),
|
||||
HermOpShifted(HermOpEO,shift),
|
||||
FineOp(Unshifted<FineField>(shift,HermOpEO,HermOpShifted)),
|
||||
SmootherOp(FineOp,P.Smoother.Shift),
|
||||
SmootherCG(P.Smoother.Order,SmootherOp),
|
||||
SmootherCheby(P.Smoother.ChebyLo,P.Smoother.ChebyHi,P.Smoother.Order,FineOp),
|
||||
LinOpC(C.CoarseOp),
|
||||
CoarseCG(P.CoarseSolver.Tol,P.CoarseSolver.MaxIt,false),
|
||||
CoarseDeflatedCG(LinOpC,CoarseCG,NoGuess),
|
||||
CoarseCheby(P.CoarseSolver.ChebyLo,P.CoarseSolver.ChebyHi,P.CoarseSolver.ChebyOrder,LinOpC),
|
||||
ADEF2(FineOp,PickSmoother(),PickCoarseSolve(),PickCoarseSolve(),C.MrhsProjectorL1,C.Deflator,C.CMrhs,C.FrbGrid),
|
||||
HermOpEOF(DdwfF),
|
||||
HermOpShiftedF(HermOpEOF,shift),
|
||||
FineOpF(Unshifted<FineFieldF>(shift,HermOpEOF,HermOpShiftedF)),
|
||||
SmootherOpF(FineOpF,P.Smoother.Shift),
|
||||
SmootherCGF(P.Smoother.Order,SmootherOpF),
|
||||
SmootherChebyF(P.Smoother.ChebyLo,P.Smoother.ChebyHi,P.Smoother.Order,FineOpF),
|
||||
ADEF2F(FineOpF,PickSmootherF(),PickCoarseSolve(),PickCoarseSolve(),C.MrhsProjectorL1,C.Deflator,C.CMrhs,C.GridsF.FrbGridF),
|
||||
PreconSeam(ADEF2F,C.FrbGrid,C.GridsF.FrbGridF,nr),
|
||||
Outer(P.Outer.Tol,P.Outer.MaxIt,FineOp,
|
||||
(P.Setup.FinePrecision==MGPrecision::fp32)
|
||||
? static_cast<MrhsPreconditioner<FineField>&>(PreconSeam)
|
||||
: static_cast<MrhsPreconditioner<FineField>&>(ADEF2),
|
||||
C.FrbGrid,
|
||||
(P.Outer.Algorithm==HDCGOuterAlgorithm::PrecBlockCGrQ) ? MrhsCGAlgorithm::PrecBlockCGrQ : MrhsCGAlgorithm::fPcg)
|
||||
{
|
||||
// BlockCGrQ assumes a stationary preconditioner: the fixed-iteration CG
|
||||
// smoother is fixed WORK but a nonlinear map, and a coarse CG solved to a
|
||||
// tolerance is not stationary either. Chebyshev on both is.
|
||||
if ( Params.Outer.Algorithm==HDCGOuterAlgorithm::PrecBlockCGrQ &&
|
||||
( Params.CoarseSolver.Method!=HDCGCoarseMethod::Chebyshev ||
|
||||
Params.Smoother.Type !=HDCGSmootherType::Chebyshev ) )
|
||||
std::cout << GridLogWarning << "HDCGSolver: PrecBlockCGrQ with a non-stationary preconditioner "
|
||||
<< "(needs Smoother.Type Chebyshev AND CoarseSolver.Method Chebyshev); expect stagnation"
|
||||
<< std::endl;
|
||||
std::cout << GridLogMessage << "HDCGSolver: Nrhs " << nr
|
||||
<< ", operator shift " << shift
|
||||
<< ", smoother " << (Params.Smoother.Type==HDCGSmootherType::CG ? "CG" : "Chebyshev")
|
||||
<< " order " << Params.Smoother.Order
|
||||
<< ", coarse " << (Params.CoarseSolver.Method==HDCGCoarseMethod::DeflatedCG ? "deflated CG" : "Chebyshev")
|
||||
<< " (deflation vectors " << C.Deflator.nev << ")"
|
||||
<< ", preconditioner fine level "
|
||||
<< (Params.Setup.FinePrecision==MGPrecision::fp32 ? "fp32 (seam at the outer CG)" : "fp64")
|
||||
<< ", outer " << (Params.Outer.Algorithm==HDCGOuterAlgorithm::PrecBlockCGrQ ? "PrecBlockCGrQ" : "fPcg")
|
||||
<< " EXACT fp64" << std::endl;
|
||||
}
|
||||
|
||||
// One right-hand side through the LinearFunction interface (nrhs must be 1)
|
||||
void Solve(const FineField &src, FineField &sol)
|
||||
{
|
||||
GRID_ASSERT( nrhs == 1 );
|
||||
sol.Checkerboard() = src.Checkerboard();
|
||||
GridStopWatch w; w.Start();
|
||||
Outer(src,sol);
|
||||
w.Stop();
|
||||
FineField Ax(src.Grid()); Ax.Checkerboard()=src.Checkerboard();
|
||||
FineOp.HermOp(sol,Ax);
|
||||
Ax=Ax-src;
|
||||
std::cout << GridLogMessage << "HDCGSolver: single rhs total " << w.Elapsed()
|
||||
<< "; FINAL Nrhs 1 (LinearFunction): true residual = " << std::sqrt(norm2(Ax)/norm2(src)) << std::endl;
|
||||
}
|
||||
void Solve(std::vector<FineField> &src, std::vector<FineField> &sol)
|
||||
{
|
||||
GRID_ASSERT( (int)src.size() == nrhs );
|
||||
for(int r=0;r<nrhs;r++) sol[r].Checkerboard() = src[r].Checkerboard();
|
||||
MemoryManager::EvictAll();
|
||||
MemoryManager::DropCache();
|
||||
|
||||
GridStopWatch w; w.Start();
|
||||
Outer(src,sol);
|
||||
w.Stop();
|
||||
std::cout << GridLogMessage << "HDCGSolver: Nrhs "<<nrhs<<" total " << w.Elapsed()
|
||||
<< " (per RHS: " << w.useconds()/1.0e6/nrhs << " s)" << std::endl;
|
||||
{
|
||||
FineField Ax(src[0].Grid()); Ax.Checkerboard()=src[0].Checkerboard();
|
||||
RealD worst=0.0;
|
||||
for(int r=0;r<nrhs;r++){
|
||||
FineOp.HermOp(sol[r],Ax);
|
||||
Ax=Ax-src[r];
|
||||
RealD rn=std::sqrt(norm2(Ax)/norm2(src[r]));
|
||||
std::cout << GridLogMessage << "FINAL Nrhs "<<nrhs<<": rhs["<<r<<"] true residual = " << rn << std::endl;
|
||||
worst=std::max(worst,rn);
|
||||
}
|
||||
std::cout << GridLogMessage << "FINAL Nrhs "<<nrhs<<": worst-case residual = " << worst << std::endl;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// HDCG refinement of the basis (the paper's loop): coarsen the SHIFTED
|
||||
// operator, deflate it, solve each raw basis vector on it with a
|
||||
// short-tolerance HDCG, keep the normalised solutions. A no-op unless
|
||||
// Subspace.Refine is HDCG and no refined basis was loaded. The caller
|
||||
// then coarsens the unshifted operator as usual.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
template<class Matrix,class MatrixF,class Coarsening>
|
||||
void HDCGRefineSubspace(Coarsening &C, Matrix &Ddwf, MatrixF &DdwfF,
|
||||
LinearOperatorBase<typename Coarsening::FineField> &HermOp, int nr)
|
||||
{
|
||||
typedef typename Coarsening::FineField FineField;
|
||||
const HDCGSubspaceParams &S = C.Params.Subspace;
|
||||
if ( S.Refine != HDCGRefineMethod::HDCG ) return;
|
||||
if ( C.FileExists(S.RefinedSubspaceFile) ) return;
|
||||
|
||||
std::cout << GridLogMessage << "HDCGRefineSubspace: shift " << S.RefineShift << " tol " << S.RefineTol
|
||||
<< " maxit " << S.RefineMaxIt << " smoother order " << S.RefineSmootherOrder << std::endl;
|
||||
ShiftedHermOpLinearOperator<FineField> Shifted(HermOp,S.RefineShift);
|
||||
HermOpAdaptor<FineField> ShiftedOp(Shifted);
|
||||
C.Coarsen(ShiftedOp,1); // refinement below still needs the RAW basis
|
||||
C.CoarseLanczos(nr);
|
||||
|
||||
HDCGMultiGridParams PR = C.Params;
|
||||
PR.Outer.Tol = S.RefineTol;
|
||||
PR.Outer.MaxIt = S.RefineMaxIt;
|
||||
PR.Smoother.Order = S.RefineSmootherOrder;
|
||||
HDCGSolver<Matrix,MatrixF,Coarsening> Refiner(Ddwf,DdwfF,C,PR,nr,S.RefineShift);
|
||||
C.RefineSubspaceHDCG(HermOp,Refiner.Outer,nr);
|
||||
|
||||
if ( S.RefinedSubspaceFile.length() )
|
||||
saveSubspace(C.rawNull, S.RefinedSubspaceFile, S.FileControl);
|
||||
}
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,177 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./Grid/algorithms/multigrid/HDCGMultiGridParams.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution
|
||||
directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// Parameters of the two-level mrhs HDCG (Hermitian, Schur-preconditioned)
|
||||
// multigrid, HDCGMultiGrid.h. Serialisable: read from XML, template written
|
||||
// on a missing file. Setup (blocking, batch, basis file, fine precision) is
|
||||
// the same struct as the PVdagM chain's; Block2 is only used when a
|
||||
// coarse-coarse level exists but must still divide the coarse lattice.
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
GRID_SERIALIZABLE_ENUM(HDCGSubspaceMethod, undef, ChebyshevNew, 1, Chebyshev, 2, FineEvecs, 3);
|
||||
GRID_SERIALIZABLE_ENUM(HDCGRefineMethod, undef, None, 1, CG, 2, HDCG, 3);
|
||||
GRID_SERIALIZABLE_ENUM(HDCGLanczosMethod, undef, None, 1, Block, 2, Plain, 3);
|
||||
GRID_SERIALIZABLE_ENUM(HDCGCoarseMethod, undef, DeflatedCG, 1, Chebyshev, 2);
|
||||
GRID_SERIALIZABLE_ENUM(HDCGSmootherType, undef, CG, 1, Chebyshev, 2);
|
||||
GRID_SERIALIZABLE_ENUM(HDCGOuterAlgorithm, undef, fPcg, 1, PrecBlockCGrQ, 2);
|
||||
|
||||
// Near-null basis. ChebyshevNew filters noise through the Hermitian operator
|
||||
// with the upper bound only; Chebyshev takes lo/hi/order; FineEvecs sums
|
||||
// FineEvecSample consecutive fp32 fine eigenvector records per basis vector.
|
||||
// A ChebyHi <= 0 means 1.1 x the power-method estimate. FileControl is the
|
||||
// scidac binary-IO control word of the basis files (0 for pre-2026 HDCG files).
|
||||
struct HDCGSubspaceParams : Serializable {
|
||||
GRID_SERIALIZABLE_CLASS_MEMBERS(HDCGSubspaceParams,
|
||||
HDCGSubspaceMethod, Method,
|
||||
RealD, ChebyHi,
|
||||
RealD, ChebyLo,
|
||||
int, ChebyOrder,
|
||||
std::string, FineEvecFile,
|
||||
int, FineEvecSample,
|
||||
int, FileControl,
|
||||
HDCGRefineMethod, Refine,
|
||||
RealD, RefineShift, // CG: the shift of the refining CG; HDCG: of the refining operator
|
||||
RealD, RefineTol,
|
||||
int, RefineMaxIt,
|
||||
int, RefineSmootherOrder, // HDCG refine only
|
||||
std::string, RefinedSubspaceFile);
|
||||
HDCGSubspaceParams()
|
||||
: Method(HDCGSubspaceMethod::ChebyshevNew), ChebyHi(0.0), ChebyLo(0.01), ChebyOrder(500),
|
||||
FineEvecFile(""), FineEvecSample(1),
|
||||
FileControl(BinaryIO::BINARYIO_LEXICOGRAPHIC|BinaryIO::BINARYIO_AGGREGATE),
|
||||
Refine(HDCGRefineMethod::None), RefineShift(1.0e-3), RefineTol(1.0e-3), RefineMaxIt(500),
|
||||
RefineSmootherOrder(11), RefinedSubspaceFile("") {};
|
||||
};
|
||||
|
||||
// Coarse eigenvectors for the deflated coarse solve: block Lanczos at the
|
||||
// solve's Nrhs, or plain Lanczos at Nrhs 1, on the same coarse operator.
|
||||
// ChebyHi <= 0 means 1.1 x the power-method estimate on the coarse operator.
|
||||
struct HDCGLanczosParams : Serializable {
|
||||
GRID_SERIALIZABLE_CLASS_MEMBERS(HDCGLanczosParams,
|
||||
HDCGLanczosMethod, Method,
|
||||
int, Nstop,
|
||||
int, Nk,
|
||||
int, Nm,
|
||||
RealD, ChebyLo,
|
||||
RealD, ChebyHi,
|
||||
int, ChebyOrder,
|
||||
RealD, Tol,
|
||||
int, MaxIt,
|
||||
std::string, EvecFile);
|
||||
HDCGLanczosParams()
|
||||
: Method(HDCGLanczosMethod::Block), Nstop(32), Nk(32), Nm(128),
|
||||
ChebyLo(0.01), ChebyHi(0.0), ChebyOrder(201), Tol(1.0e-4), MaxIt(100), EvecFile("") {};
|
||||
};
|
||||
|
||||
struct HDCGCoarseSolverParams : Serializable {
|
||||
GRID_SERIALIZABLE_CLASS_MEMBERS(HDCGCoarseSolverParams,
|
||||
HDCGCoarseMethod, Method,
|
||||
RealD, Tol, // DeflatedCG
|
||||
int, MaxIt,
|
||||
RealD, ChebyLo, // Chebyshev polynomial inverse
|
||||
RealD, ChebyHi,
|
||||
int, ChebyOrder);
|
||||
HDCGCoarseSolverParams()
|
||||
: Method(HDCGCoarseMethod::DeflatedCG), Tol(5.0e-2), MaxIt(3000),
|
||||
ChebyLo(1.0e-2), ChebyHi(40.0), ChebyOrder(120) {};
|
||||
};
|
||||
|
||||
// CG: Order iterations of CG on the operator shifted by Shift.
|
||||
// Chebyshev: order Order polynomial on [ChebyLo,ChebyHi] of the unshifted operator.
|
||||
struct HDCGSmootherParams : Serializable {
|
||||
GRID_SERIALIZABLE_CLASS_MEMBERS(HDCGSmootherParams,
|
||||
HDCGSmootherType, Type,
|
||||
RealD, Shift,
|
||||
int, Order,
|
||||
RealD, ChebyLo,
|
||||
RealD, ChebyHi);
|
||||
HDCGSmootherParams() : Type(HDCGSmootherType::CG), Shift(2.0), Order(7), ChebyLo(2.0), ChebyHi(92.0) {};
|
||||
};
|
||||
|
||||
struct HDCGOuterParams : Serializable {
|
||||
GRID_SERIALIZABLE_CLASS_MEMBERS(HDCGOuterParams,
|
||||
RealD, Tol,
|
||||
int, MaxIt,
|
||||
HDCGOuterAlgorithm, Algorithm);
|
||||
HDCGOuterParams() : Tol(1.0e-8), MaxIt(500), Algorithm(HDCGOuterAlgorithm::fPcg) {};
|
||||
};
|
||||
|
||||
struct HDCGMultiGridParams : Serializable {
|
||||
GRID_SERIALIZABLE_CLASS_MEMBERS(HDCGMultiGridParams,
|
||||
MGSetupParams, Setup,
|
||||
HDCGSubspaceParams, Subspace,
|
||||
HDCGLanczosParams, Lanczos,
|
||||
HDCGCoarseSolverParams, CoarseSolver,
|
||||
HDCGSmootherParams, Smoother,
|
||||
HDCGOuterParams, Outer);
|
||||
HDCGMultiGridParams() {};
|
||||
};
|
||||
|
||||
inline void CheckValidity(const HDCGMultiGridParams &P)
|
||||
{
|
||||
GRID_ASSERT( P.Setup.Block1.size() == 4 );
|
||||
GRID_ASSERT( P.Setup.Block2.size() == 4 );
|
||||
GRID_ASSERT( P.Setup.CoarsenBatch >= 1 );
|
||||
GRID_ASSERT( P.Setup.FinePrecision != MGPrecision::undef );
|
||||
GRID_ASSERT( P.Subspace.Method != HDCGSubspaceMethod::undef );
|
||||
GRID_ASSERT( P.Subspace.Refine != HDCGRefineMethod::undef );
|
||||
GRID_ASSERT( P.Subspace.FineEvecSample >= 1 );
|
||||
GRID_ASSERT( P.Lanczos.Method != HDCGLanczosMethod::undef );
|
||||
GRID_ASSERT( P.Lanczos.Nstop <= P.Lanczos.Nk && P.Lanczos.Nk <= P.Lanczos.Nm );
|
||||
GRID_ASSERT( P.CoarseSolver.Method != HDCGCoarseMethod::undef );
|
||||
GRID_ASSERT( P.CoarseSolver.Tol > 0.0 );
|
||||
GRID_ASSERT( P.Smoother.Type != HDCGSmootherType::undef );
|
||||
GRID_ASSERT( P.Smoother.Order > 0 );
|
||||
GRID_ASSERT( P.Outer.Tol > 0.0 );
|
||||
GRID_ASSERT( P.Outer.Algorithm != HDCGOuterAlgorithm::undef );
|
||||
}
|
||||
|
||||
// As ReadPVdagMMultiGridParams: a missing file gets a template written next
|
||||
// to it and false returned; an empty filename means defaults.
|
||||
inline bool ReadHDCGMultiGridParams(HDCGMultiGridParams &P, const std::string &xmlfile)
|
||||
{
|
||||
if ( xmlfile.length() ) {
|
||||
bool good;
|
||||
{ std::ifstream f(xmlfile); good = f.good(); }
|
||||
if ( !good ) {
|
||||
std::cout << GridLogMessage << "HDCGMultiGridParams: " << xmlfile << " does not exist" << std::endl;
|
||||
if ( GlobalSharedMemory::WorldRank == 0 ) {
|
||||
XmlWriter WR(xmlfile+".templ");
|
||||
write(WR, "HDCGMultiGridParams", P);
|
||||
std::cout << GridLogMessage << "HDCGMultiGridParams: template written to "
|
||||
<< xmlfile << ".templ" << std::endl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
XmlReader RD(xmlfile);
|
||||
read(RD, "HDCGMultiGridParams", P);
|
||||
}
|
||||
CheckValidity(P);
|
||||
std::cout << GridLogMessage << "HDCGMultiGridParams ("
|
||||
<< (xmlfile.length() ? xmlfile : std::string("defaults")) << "):" << std::endl;
|
||||
std::cout << P << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,433 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./Grid/algorithms/multigrid/MrhsMultiGrid.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution
|
||||
directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
#include <Grid/algorithms/deflation/MultiRHSBlockProject.h>
|
||||
#include <Grid/algorithms/multigrid/MrhsPreconditioner.h>
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Single-polynomial mrhs PGCR: one step length and one set of
|
||||
// orthogonalisation coefficients SHARED across the right-hand sides
|
||||
// (vnorm2/vinnerProduct sum over rhs). For DWF the dense spectrum
|
||||
// makes sharing near-free and the summed reductions amortise; see the
|
||||
// mrhs-HDCG paper (arXiv:2409.03904). Orthogonalisation is classical
|
||||
// Gram-Schmidt, batched per rhs (rankInnerProductMulti/axpyMulti) with
|
||||
// ONE GlobalSumVector for all coefficients.
|
||||
//
|
||||
// OnStep is a programmatic per-step hook for algorithmic studies
|
||||
// (e.g. driving the GCR coefficient recorder); it is not a consumer
|
||||
// parameter.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
template<class Field>
|
||||
class MrhsPGCRNonHermitian {
|
||||
public:
|
||||
RealD Tolerance; Integer MaxIterations; int mmax,nstep,steps,level;
|
||||
int ZeroGuess = 0; int FirstCycle = 0;
|
||||
std::string name = "Level 1";
|
||||
// Trace range names carry the instance name, so a profile separates the
|
||||
// solvers that otherwise nest as one shared range.
|
||||
std::string trace_op = "MrhsPGCR::vOp", trace_orthog = "MrhsPGCR orthog";
|
||||
LinearOperatorBase<Field> &Linop;
|
||||
MrhsLinearFunction<Field> &Preconditioner;
|
||||
std::function<void(int)> OnStep; // called with the outer step count after every step
|
||||
|
||||
void Level(int lv){
|
||||
name = "Level " + std::to_string(lv);
|
||||
level=lv;
|
||||
}
|
||||
|
||||
void Name(std::string n){
|
||||
name = n;
|
||||
trace_op = name+" MrhsPGCR::vOp";
|
||||
trace_orthog = name+" MrhsPGCR orthog";
|
||||
}
|
||||
|
||||
void SetZeroGuess(int z)
|
||||
{
|
||||
ZeroGuess=z;
|
||||
}
|
||||
|
||||
MrhsPGCRNonHermitian(RealD tol,Integer maxit,LinearOperatorBase<Field> &_Linop,MrhsLinearFunction<Field> &Prec,int _mmax,int _nstep)
|
||||
: Tolerance(tol),MaxIterations(maxit),Linop(_Linop),Preconditioner(Prec),mmax(_mmax),nstep(_nstep)
|
||||
{
|
||||
level=1;
|
||||
}
|
||||
|
||||
static RealD vnorm2(std::vector<Field> &x)
|
||||
{
|
||||
RealD s=0;
|
||||
for(auto &f:x) s+=norm2(f);
|
||||
return s;
|
||||
}
|
||||
|
||||
static ComplexD vinnerProduct(std::vector<Field> &x,std::vector<Field> &y){
|
||||
ComplexD s(0);
|
||||
for(int r=0;r<(int)x.size();r++) {
|
||||
s+=innerProduct(x[r],y[r]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static void vaxpy(std::vector<Field> &z,ComplexD a,std::vector<Field> &x,std::vector<Field> &y)
|
||||
{
|
||||
for(int r=0;r<(int)z.size();r++) {
|
||||
axpy(z[r],a,x[r],y[r]);
|
||||
}
|
||||
}
|
||||
|
||||
void vOp(std::vector<Field> &in,std::vector<Field> &out)
|
||||
{
|
||||
GRID_TRACE(trace_op.c_str());
|
||||
for(int r=0;r<(int)in.size();r++) {
|
||||
Linop.Op(in[r],out[r]);
|
||||
}
|
||||
}
|
||||
void operator()(std::vector<Field> &src,std::vector<Field> &psi){
|
||||
RealD cp,ssq,rsq;
|
||||
int nrhs=src.size();
|
||||
GridBase *grid=src[0].Grid();
|
||||
ssq=vnorm2(src);
|
||||
rsq=Tolerance*Tolerance*ssq;
|
||||
|
||||
std::vector<Field> r(nrhs,grid);
|
||||
GridStopWatch T;
|
||||
T.Start();
|
||||
steps=0;
|
||||
FirstCycle=1;
|
||||
for(int k=0;k<MaxIterations;k++){
|
||||
cp=GCRnStep(src,psi,rsq);
|
||||
std::cout<<GridLogMessage<<std::string(level,'\t')<<" "<<name<<" MrhsPGCR("<<mmax<<","<<nstep<<") "<<steps
|
||||
<<" steps cp = "<<cp<<" target "<<rsq<<std::endl;
|
||||
if(cp<rsq){
|
||||
T.Stop(); vOp(psi,r); for(int rr=0;rr<nrhs;rr++) axpy(r[rr],-1.0,src[rr],r[rr]);
|
||||
RealD tr=vnorm2(r);
|
||||
std::cout<<GridLogMessage<<std::string(level,'\t')<<" "<<name<<" MrhsPGCR: Converged on iteration "<<steps
|
||||
<<" computed residual "<<std::sqrt(cp/ssq)<<" true residual "<<std::sqrt(tr/ssq)<<" target "<<Tolerance<<std::endl;
|
||||
std::cout<<GridLogMessage<<std::string(level,'\t')<<" "<<name<<" MrhsPGCR Time elapsed: Total "<<T.Elapsed()<<std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
std::cout<<GridLogMessage<<"MrhsPGCR: did not converge"<<std::endl;
|
||||
}
|
||||
RealD GCRnStep(std::vector<Field> &src,std::vector<Field> &psi,RealD rsq)
|
||||
{
|
||||
RealD cp;
|
||||
ComplexD a,rq;
|
||||
int nrhs=src.size();
|
||||
GridBase *grid=src[0].Grid();
|
||||
|
||||
std::vector<Field> r(nrhs,grid),Az(nrhs,grid); // Az: restart residual scratch only
|
||||
std::vector< std::vector<Field> > q(mmax,std::vector<Field>(nrhs,grid));
|
||||
std::vector< std::vector<Field> > p(mmax,std::vector<Field>(nrhs,grid));
|
||||
std::vector<RealD> qq(mmax);
|
||||
|
||||
if (ZeroGuess && FirstCycle) {
|
||||
for(int rr=0;rr<nrhs;rr++){
|
||||
psi[rr]=Zero();
|
||||
r[rr]=src[rr];
|
||||
}
|
||||
} else {
|
||||
vOp(psi,Az);
|
||||
for(int rr=0;rr<nrhs;rr++) {
|
||||
r[rr]=src[rr]-Az[rr];
|
||||
}
|
||||
}
|
||||
|
||||
FirstCycle=0;
|
||||
// p[0]=Prec(r), q[0]=A p[0], produced directly in the history slots (no copies)
|
||||
|
||||
Preconditioner(r,p[0]);
|
||||
vOp(p[0],q[0]);
|
||||
qq[0]=vnorm2(q[0]);
|
||||
cp=vnorm2(r);
|
||||
|
||||
for(int k=0;k<nstep;k++){
|
||||
steps++;
|
||||
int kp=k+1, peri_k=k%mmax, peri_kp=kp%mmax;
|
||||
|
||||
if ( OnStep ) OnStep(steps);
|
||||
|
||||
rq=vinnerProduct(q[peri_k],r);
|
||||
a=rq/qq[peri_k];
|
||||
vaxpy(psi,a,p[peri_k],psi);
|
||||
vaxpy(r,-a,q[peri_k],r);
|
||||
cp=vnorm2(r);
|
||||
|
||||
std::cout<<GridLogMessage<<std::string(level,'\t')<<" "<<name<<" MrhsPGCR step["<<steps<<"] resid "<<cp<<" target "<<rsq<<std::endl;
|
||||
|
||||
if((k==nstep-1)||(cp<rsq)) return cp;
|
||||
|
||||
// New direction straight into its history slot: p=Prec(r), q=A p.
|
||||
Preconditioner(r,p[peri_kp]);
|
||||
|
||||
vOp(p[peri_kp],q[peri_kp]);
|
||||
|
||||
int northog=((kp)>(mmax-1))?(mmax-1):(kp);
|
||||
|
||||
{
|
||||
GRID_TRACE(trace_orthog.c_str());
|
||||
// Classical Gram-Schmidt: all coefficients against the UN-updated new q
|
||||
// (independent, batchable), then apply. Complex coefficient: the
|
||||
// operator is non-Hermitian, real(<q_j,Aq>) alone left q's non-orthogonal.
|
||||
// Batched per rhs (one fused kernel + one reduction each), the shared
|
||||
// coefficient summed over rhs on the host, ONE GlobalSumVector.
|
||||
std::vector<ComplexD> bcoef(northog,ComplexD(0.0)), part;
|
||||
for(int rr=0;rr<nrhs;rr++){
|
||||
std::vector<const Field*> qwin(northog);
|
||||
for(int back=0;back<northog;back++){
|
||||
int peri_back=(k-back)%mmax;
|
||||
GRID_ASSERT((k-back)>=0);
|
||||
qwin[back]=&q[peri_back][rr];
|
||||
}
|
||||
|
||||
rankInnerProductMulti(part,qwin,q[peri_kp][rr]);
|
||||
|
||||
for(int back=0;back<northog;back++) bcoef[back]+=part[back];
|
||||
}
|
||||
|
||||
if(northog) grid->GlobalSumVector(&bcoef[0],northog);
|
||||
|
||||
for(int back=0;back<northog;back++){
|
||||
int peri_back=(k-back)%mmax;
|
||||
bcoef[back]=-bcoef[back]/qq[peri_back];
|
||||
}
|
||||
for(int rr=0;rr<nrhs;rr++){
|
||||
std::vector<const Field*> qwin(northog), pwin(northog);
|
||||
for(int back=0;back<northog;back++){
|
||||
int peri_back=(k-back)%mmax;
|
||||
qwin[back]=&q[peri_back][rr];
|
||||
pwin[back]=&p[peri_back][rr];
|
||||
}
|
||||
axpyMulti(p[peri_kp][rr],bcoef,pwin);
|
||||
axpyMulti(q[peri_kp][rr],bcoef,qwin);
|
||||
}
|
||||
}
|
||||
qq[peri_kp]=vnorm2(q[peri_kp]);
|
||||
}
|
||||
GRID_ASSERT(0); return cp;
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Dense L3 solve on the packed D+1 coarse-coarse field. Adapts any
|
||||
// class exposing ApplyBatch6D (DenseCoarseMatrix) to a LinearFunction.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
template<class DenseType, class CoarseCoarseField>
|
||||
class MrhsDenseCCSolve : public LinearFunction<CoarseCoarseField> {
|
||||
public:
|
||||
DenseType &_Dense;
|
||||
int _nrhs;
|
||||
MrhsDenseCCSolve(DenseType &D, int nrhs) : _Dense(D), _nrhs(nrhs) {}
|
||||
using LinearFunction<CoarseCoarseField>::operator();
|
||||
virtual void operator()(const CoarseCoarseField &in, CoarseCoarseField &out){
|
||||
_Dense.ApplyBatch6D(in, out, _nrhs);
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// L2->L3 half V-cycle on the D+1 coarse field: coarse-coarse correction
|
||||
// through the mixed blockProject, then post-smooth.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
template<class CoarseField, class CoarseCoarseField>
|
||||
class MrhsCoarseThreeLevelPrec : public LinearFunction<CoarseField> {
|
||||
public:
|
||||
LinearOperatorBase<CoarseField> &_CoarseOp;
|
||||
LinearFunction<CoarseField> &_CoarseSmoother;
|
||||
MultiRHSBlockProject<CoarseField> &_Projector;
|
||||
LinearFunction<CoarseCoarseField> &_CoarseCoarseSolve;
|
||||
GridBase *_Coarse5d, *_CoarseCoarse5d, *_CoarseCoarseMrhs;
|
||||
int _nrhs;
|
||||
MrhsCoarseThreeLevelPrec(LinearOperatorBase<CoarseField> &CoarseOp,
|
||||
LinearFunction<CoarseField> &CoarseSmoother,
|
||||
MultiRHSBlockProject<CoarseField> &Projector,
|
||||
LinearFunction<CoarseCoarseField> &CoarseCoarseSolve,
|
||||
GridBase *Coarse5d, GridBase *CoarseCoarse5d, GridBase *CoarseCoarseMrhs, int nrhs)
|
||||
: _CoarseOp(CoarseOp), _CoarseSmoother(CoarseSmoother), _Projector(Projector),
|
||||
_CoarseCoarseSolve(CoarseCoarseSolve),
|
||||
_Coarse5d(Coarse5d), _CoarseCoarse5d(CoarseCoarse5d), _CoarseCoarseMrhs(CoarseCoarseMrhs), _nrhs(nrhs) {}
|
||||
using LinearFunction<CoarseField>::operator();
|
||||
virtual void operator()(const CoarseField &in, CoarseField &out) {
|
||||
GRID_TRACE("MGCoarseVcycle");
|
||||
CoarseField vec1(in.Grid());
|
||||
CoarseField vec2(in.Grid());
|
||||
CoarseCoarseField CCsrc(_CoarseCoarseMrhs);
|
||||
CoarseCoarseField CCsol(_CoarseCoarseMrhs);
|
||||
// The cycle starts from x0 = in, one unit-step Richardson iteration ahead of
|
||||
// a zero start; measured, it pays for its apply by cutting Couter steps 22%.
|
||||
// x0 is never materialised in out: the operator is applied to in directly and
|
||||
// the copy folds into the add that lands the coarse-coarse correction.
|
||||
{
|
||||
GRID_TRACE("MGCoarseResidual");
|
||||
_CoarseOp.Op(in,vec1); sub(vec1,in,vec1);
|
||||
}
|
||||
// restrict, through the mixed blockProject: D+1 coarse in, D+1 cc out
|
||||
{
|
||||
GRID_TRACE("MGCCProject");
|
||||
_Projector.blockProject(vec1,CCsrc);
|
||||
}
|
||||
{
|
||||
GRID_TRACE("MGCCSolve");
|
||||
_CoarseCoarseSolve(CCsrc,CCsol);
|
||||
}
|
||||
{
|
||||
GRID_TRACE("MGCCPromote");
|
||||
_Projector.blockPromote(vec1,CCsol);
|
||||
add(out,in,vec1);
|
||||
}
|
||||
{
|
||||
GRID_TRACE("MGCoarseResidual2");
|
||||
_CoarseOp.Op(out,vec1); sub(vec1,in,vec1);
|
||||
}
|
||||
{
|
||||
GRID_TRACE("MGCoarseSmooth");
|
||||
_CoarseSmoother(vec1,vec2);
|
||||
add(out,out,vec2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// L1->L2 mrhs V-cycle. The whole V-cycle is preconditioner: its fine
|
||||
// residuals and the smoother may run with sloppy halos; the caller
|
||||
// (the outer Krylov) gets the exact operator back on exit. SetSloppy
|
||||
// is wired by the composer to PVdagMLinearOperator::SloppyComms (a
|
||||
// no-op by default), replacing the file-scope global the example used.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Projector_t defaults to a transfer operator whose STORE matches FineField,
|
||||
// which is the case whenever the coarse sector and the fine level share a
|
||||
// precision; pass it explicitly when they differ.
|
||||
template<class FineField, class MrhsCoarseVector, class FineSmoother,
|
||||
class Projector_t = MultiRHSBlockProject<FineField> >
|
||||
class MrhsTwoLevelMG : public MrhsPreconditioner<FineField> {
|
||||
public:
|
||||
typedef MrhsCoarseVector CoarseVector;
|
||||
LinearOperatorBase<FineField> &_FineOperator;
|
||||
FineSmoother &_PostSmoother;
|
||||
Projector_t &_Projector; // store precision need not match FineField
|
||||
LinearFunction<CoarseVector> &_CoarseSolve;
|
||||
GridBase *_CoarseGrid, *_CoarseGridMrhs;
|
||||
std::function<void(int)> SetSloppy = [](int){};
|
||||
int SloppyComms = 0; // value passed to SetSloppy on entry
|
||||
|
||||
MrhsTwoLevelMG(LinearOperatorBase<FineField> &FineOp, FineSmoother &Post,
|
||||
Projector_t &Projector, LinearFunction<CoarseVector> &CoarseSolve,
|
||||
GridBase *CoarseGrid, GridBase *CoarseGridMrhs)
|
||||
: _FineOperator(FineOp),_PostSmoother(Post),_Projector(Projector),_CoarseSolve(CoarseSolve),
|
||||
_CoarseGrid(CoarseGrid),_CoarseGridMrhs(CoarseGridMrhs){}
|
||||
|
||||
virtual void operator()(std::vector<FineField> &in, std::vector<FineField> &out)
|
||||
{
|
||||
GRID_TRACE("MGVcycle");
|
||||
SetSloppy(SloppyComms);
|
||||
int nrhs=in.size(); GridBase *fgrid=in[0].Grid();
|
||||
std::vector<FineField> vec1(nrhs,fgrid),vec2(nrhs,fgrid);
|
||||
// x0 = in, never materialised in out: the operator is applied to in directly
|
||||
// and the copy folds into the add that lands the coarse correction.
|
||||
{
|
||||
GRID_TRACE("MGFineResidual");
|
||||
for(int r=0;r<nrhs;r++){ _FineOperator.Op(in[r],vec1[r]); sub(vec1[r],in[r],vec1[r]); }
|
||||
}
|
||||
// fine vector -> D+1 coarse, via the mixed blockProject
|
||||
CoarseVector CsrcMrhs(_CoarseGridMrhs), CsolMrhs(_CoarseGridMrhs);
|
||||
{
|
||||
GRID_TRACE("MGProject");
|
||||
_Projector.blockProject(vec1,CsrcMrhs);
|
||||
}
|
||||
CsolMrhs=Zero();
|
||||
{
|
||||
GRID_TRACE("MGCoarseSolve");
|
||||
_CoarseSolve(CsrcMrhs,CsolMrhs);
|
||||
}
|
||||
{
|
||||
GRID_TRACE("MGPromote");
|
||||
_Projector.blockPromote(vec1,CsolMrhs);
|
||||
for(int r=0;r<nrhs;r++) add(out[r],in[r],vec1[r]);
|
||||
}
|
||||
{
|
||||
GRID_TRACE("MGFineResidual2");
|
||||
for(int r=0;r<nrhs;r++){ _FineOperator.Op(out[r],vec1[r]); sub(vec1[r],in[r],vec1[r]); }
|
||||
}
|
||||
{
|
||||
GRID_TRACE("MGPostSmooth");
|
||||
for(int r=0;r<nrhs;r++){
|
||||
_PostSmoother(vec1[r],vec2[r]); add(out[r],out[r],vec2[r]);
|
||||
}
|
||||
}
|
||||
SetSloppy(0);
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// The fp64/fp32 seam of the solve chain. The outer Krylov hands fp64
|
||||
// residuals to its preconditioner; this adapter converts them to fp32,
|
||||
// runs an fp32 preconditioner (the whole V-cycle), and converts the
|
||||
// correction back. Two precisionChange per outer step per rhs, on
|
||||
// workspaces built once. The outer operator never sees fp32.
|
||||
//
|
||||
// The fp32 scratch is allocated ON FIRST USE, not in the constructor: the
|
||||
// solver builds this seam whichever precision is selected, and at Nrhs 12
|
||||
// on a 48^3x96 Ls=24 rank the scratch is 2 GB of device memory. A seam
|
||||
// that is never called must cost nothing. Same idiom as the GCR history
|
||||
// vectors: re-made only if the grid or the rhs count changes.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
template<class FieldD, class FieldF>
|
||||
class MrhsMixedPrecPreconditioner : public MrhsPreconditioner<FieldD> {
|
||||
public:
|
||||
MrhsPreconditioner<FieldF> &_Inner;
|
||||
precisionChangeWorkspace _ws_d2f; // out fp32, in fp64
|
||||
precisionChangeWorkspace _ws_f2d; // out fp64, in fp32
|
||||
GridBase *_gridF;
|
||||
std::vector<FieldF> _in_f, _out_f;
|
||||
|
||||
MrhsMixedPrecPreconditioner(MrhsPreconditioner<FieldF> &Inner, GridBase *gridD, GridBase *gridF, int nrhs)
|
||||
: _Inner(Inner), _ws_d2f(gridF,gridD), _ws_f2d(gridD,gridF), _gridF(gridF) {}
|
||||
|
||||
void Scratch(int nrhs){
|
||||
if ( (int)_in_f.size() == nrhs ) return;
|
||||
_in_f.clear(); _in_f.reserve(nrhs);
|
||||
_out_f.clear(); _out_f.reserve(nrhs);
|
||||
for(int r=0;r<nrhs;r++){ _in_f.emplace_back(_gridF); _out_f.emplace_back(_gridF); }
|
||||
}
|
||||
|
||||
virtual void operator()(std::vector<FieldD> &in, std::vector<FieldD> &out)
|
||||
{
|
||||
GRID_TRACE("MGPrecisionSeam");
|
||||
int nrhs=in.size(); Scratch(nrhs);
|
||||
for(int r=0;r<nrhs;r++) precisionChange(_in_f[r],in[r],_ws_d2f);
|
||||
_Inner(_in_f,_out_f);
|
||||
for(int r=0;r<nrhs;r++) precisionChange(out[r],_out_f[r],_ws_f2d);
|
||||
}
|
||||
// The start and the timers are the inner preconditioner's
|
||||
virtual void Vstart(std::vector<FieldD> &x, std::vector<FieldD> &src)
|
||||
{
|
||||
GRID_TRACE("MGPrecisionSeamVstart");
|
||||
int nrhs=src.size(); Scratch(nrhs);
|
||||
for(int r=0;r<nrhs;r++) precisionChange(_in_f[r],src[r],_ws_d2f);
|
||||
_Inner.Vstart(_out_f,_in_f);
|
||||
for(int r=0;r<nrhs;r++) precisionChange(x[r],_out_f[r],_ws_f2d);
|
||||
}
|
||||
virtual void ResetTimers(void) { _Inner.ResetTimers(); }
|
||||
virtual void ReportTimers(const std::string &prefix) { _Inner.ReportTimers(prefix); }
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,73 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./Grid/algorithms/multigrid/MrhsPreconditioner.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution
|
||||
directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// The mrhs interfaces. This header is in the Algorithms.h include
|
||||
// chain (AdefMrhs.h needs it); the concrete mrhs classes are in
|
||||
// MrhsMultiGrid.h, reached through PVdagMMultiGrid.h / HDCGMultiGrid.h.
|
||||
//
|
||||
// MrhsLinearFunction: vector-of-fields in, vector out. The outer level
|
||||
// carries mrhs as std::vector<Field>; below it mrhs is PACKED into a
|
||||
// single D+1 field (rhs = dim 0) and the coarse classes are plain
|
||||
// LinearFunctions on that.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
template<class Field>
|
||||
class MrhsLinearFunction {
|
||||
public:
|
||||
virtual void operator()(std::vector<Field> &in, std::vector<Field> &out) = 0;
|
||||
virtual ~MrhsLinearFunction(){};
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// An mrhs preconditioner for the two-level CG family (AdefMrhs.h):
|
||||
// operator() is M1; Vstart chooses x_0 from the source (Tang's ADEF-2
|
||||
// start x_0 = Q b; the default is the zero start). The timers are the
|
||||
// M1 breakdown the outer solver reports.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
template<class Field>
|
||||
class MrhsPreconditioner : public MrhsLinearFunction<Field> {
|
||||
public:
|
||||
GridStopWatch ProjectTimer, PromoteTimer, DeflateTimer, CoarseTimer, FineTimer, SmoothTimer;
|
||||
virtual void Vstart(std::vector<Field> &x, std::vector<Field> &src)
|
||||
{
|
||||
for(int r=0;r<(int)x.size();r++) x[r] = Zero();
|
||||
}
|
||||
virtual void ResetTimers(void)
|
||||
{
|
||||
ProjectTimer.Reset(); PromoteTimer.Reset(); DeflateTimer.Reset();
|
||||
CoarseTimer.Reset(); FineTimer.Reset(); SmoothTimer.Reset();
|
||||
}
|
||||
virtual void ReportTimers(const std::string &prefix)
|
||||
{
|
||||
std::cout<<GridLogMessage<<"**** M1 breakdown:"<<std::endl;
|
||||
std::cout<<GridLogMessage<<prefix<<"Project "<<ProjectTimer.Elapsed()<<std::endl;
|
||||
std::cout<<GridLogMessage<<prefix<<"Promote "<<PromoteTimer.Elapsed()<<std::endl;
|
||||
std::cout<<GridLogMessage<<prefix<<"Deflate "<<DeflateTimer.Elapsed()<<std::endl;
|
||||
std::cout<<GridLogMessage<<prefix<<"Coarse "<<CoarseTimer.Elapsed()<<std::endl;
|
||||
std::cout<<GridLogMessage<<prefix<<"Fine "<<FineTimer.Elapsed()<<std::endl;
|
||||
std::cout<<GridLogMessage<<prefix<<"Smooth "<<SmoothTimer.Elapsed()<<std::endl;
|
||||
}
|
||||
virtual ~MrhsPreconditioner(){};
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,161 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./lib/algorithms/multigrid/MrhsPromotedOperator.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Present a D dimensional operator as a D+1 dimensional operator with Nrhs in
|
||||
// dimension 0. Field type is unchanged; only the Grid differs, so this is a
|
||||
// LinearOperatorBase and callers need no template parameter: a native mrhs
|
||||
// operator derives from the same base and substitutes without a call site
|
||||
// change.
|
||||
//
|
||||
// Slices in and out around the wrapped operator. No arithmetic beyond the
|
||||
// wrapped call, but one ExtractSliceFast/InsertSliceFast pair per right hand
|
||||
// side: data motion, not work.
|
||||
//
|
||||
// AdjOp is carried so that A^dag may be coarsened as a separate coarse
|
||||
// operator when needed, rather than doubling coarse storage.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
template<class Field>
|
||||
class MrhsPromotedOperator : public LinearOperatorBase<Field>
|
||||
{
|
||||
private:
|
||||
|
||||
LinearOperatorBase<Field> &_LinOp;
|
||||
GridBase *_LowGrid;
|
||||
int _Nrhs;
|
||||
|
||||
public:
|
||||
|
||||
MrhsPromotedOperator(LinearOperatorBase<Field> &LinOp,GridBase *LowGrid,int Nrhs)
|
||||
: _LinOp(LinOp), _LowGrid(LowGrid), _Nrhs(Nrhs)
|
||||
{
|
||||
GRID_ASSERT(_Nrhs>=1);
|
||||
}
|
||||
|
||||
GridBase *LowGrid(void) { return _LowGrid; }
|
||||
int Nrhs(void) { return _Nrhs; }
|
||||
|
||||
// Reset on each call; retrieve and accumulate in the caller
|
||||
RealD tslice;
|
||||
RealD top;
|
||||
|
||||
void OpDiag (const Field &in, Field &out)
|
||||
{
|
||||
SliceLoop(in,out,[&](Field &i,Field &o){ _LinOp.OpDiag(i,o); });
|
||||
}
|
||||
|
||||
void Op (const Field &in, Field &out)
|
||||
{
|
||||
SliceLoop(in,out,[&](Field &i,Field &o){ _LinOp.Op(i,o); });
|
||||
}
|
||||
|
||||
void AdjOp (const Field &in, Field &out)
|
||||
{
|
||||
SliceLoop(in,out,[&](Field &i,Field &o){ _LinOp.AdjOp(i,o); });
|
||||
}
|
||||
|
||||
void HermOp (const Field &in, Field &out)
|
||||
{
|
||||
SliceLoop(in,out,[&](Field &i,Field &o){ _LinOp.HermOp(i,o); });
|
||||
}
|
||||
|
||||
void OpDir (const Field &in, Field &out,int dir,int disp)
|
||||
{
|
||||
SliceLoop(in,out,[&](Field &i,Field &o){ _LinOp.OpDir(i,o,dir,disp); });
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// Norms of the D+1 field are the sums over slices
|
||||
//////////////////////////////////////////////////////////////////
|
||||
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2)
|
||||
{
|
||||
Conformable(in,out);
|
||||
Field lo_in (_LowGrid);
|
||||
Field lo_out(_LowGrid);
|
||||
n1=0.0;
|
||||
n2=0.0;
|
||||
for(int r=0;r<_Nrhs;r++){
|
||||
RealD r1,r2;
|
||||
ExtractSliceFast(lo_in,in,r,0);
|
||||
_LinOp.HermOpAndNorm(lo_in,lo_out,r1,r2);
|
||||
InsertSliceFast(lo_out,out,r,0);
|
||||
n1=n1+r1;
|
||||
n2=n2+r2;
|
||||
}
|
||||
}
|
||||
|
||||
void OpDirAll(const Field &in, std::vector<Field> &out)
|
||||
{
|
||||
int npoint = out.size();
|
||||
Field lo_in(_LowGrid);
|
||||
std::vector<Field> lo_out(npoint,_LowGrid);
|
||||
for(int r=0;r<_Nrhs;r++){
|
||||
ExtractSliceFast(lo_in,in,r,0);
|
||||
_LinOp.OpDirAll(lo_in,lo_out);
|
||||
for(int p=0;p<npoint;p++){
|
||||
InsertSliceFast(lo_out[p],out[p],r,0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void Conformable(const Field &in,const Field &out)
|
||||
{
|
||||
conformable(in.Grid(),out.Grid());
|
||||
GRID_ASSERT(in.Grid()->_ndimension == _LowGrid->_ndimension+1);
|
||||
GRID_ASSERT(in.Grid()->_fdimensions[0] == _Nrhs);
|
||||
}
|
||||
|
||||
template<class Kernel>
|
||||
void SliceLoop(const Field &in,Field &out,Kernel K)
|
||||
{
|
||||
Conformable(in,out);
|
||||
Field lo_in (_LowGrid);
|
||||
Field lo_out(_LowGrid);
|
||||
tslice=0.0;
|
||||
top=0.0;
|
||||
for(int r=0;r<_Nrhs;r++){
|
||||
tslice-=usecond();
|
||||
ExtractSliceFast(lo_in,in,r,0);
|
||||
tslice+=usecond();
|
||||
top-=usecond();
|
||||
K(lo_in,lo_out);
|
||||
top+=usecond();
|
||||
tslice-=usecond();
|
||||
InsertSliceFast(lo_out,out,r,0);
|
||||
tslice+=usecond();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,51 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: Grid/algorithms/multigrid/MultiGrid.h
|
||||
|
||||
Copyright (C) 2023
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
#include <Grid/algorithms/multigrid/Aggregates.h>
|
||||
#include <Grid/algorithms/multigrid/Geometry.h>
|
||||
#include <Grid/algorithms/multigrid/CoarsenedMatrix.h>
|
||||
#include <Grid/algorithms/multigrid/GeneralCoarsenedMatrixMultiRHS.h>
|
||||
// DEPRECATED: the superseded coarse operators and the Aggregation-based
|
||||
// single-RHS ADEF-2. Nothing in the library uses them; the PVdagM and HDCG
|
||||
// chains are on MultiGeneralCoarsenedOperator (PVdagMMultiGrid.h,
|
||||
// HDCGMultiGrid.h). Kept so the pre-2026 drivers in tests/debug and
|
||||
// examples still build; removing this block is the deletion gate for them.
|
||||
#include <Grid/algorithms/multigrid/deprecated/GeneralCoarsenedMatrix.h>
|
||||
#include <Grid/algorithms/multigrid/deprecated/GeneralCoarsenedMatrixMultiRHS.h>
|
||||
#include <Grid/algorithms/multigrid/deprecated/TwoLevelADEF2.h>
|
||||
#include <Grid/algorithms/multigrid/MrhsPromotedOperator.h>
|
||||
#include <Grid/algorithms/multigrid/Smoothers.h>
|
||||
#include <Grid/algorithms/multigrid/PVdagMMultiGridParams.h>
|
||||
// PVdagMOperators.h / MrhsMultiGrid.h / PVdagMMultiGrid.h /
|
||||
// DenseCoarseMatrix.h / MultiGridIO.h / HDCGMultiGrid.h are NOT in this
|
||||
// umbrella: consumers include PVdagMMultiGrid.h or HDCGMultiGrid.h
|
||||
// explicitly (they pull the dense stack, BLAS, and the scidac I/O, which
|
||||
// is declared after Algorithms.h in Grid.h). Keeping MrhsMultiGrid.h out
|
||||
// of the Algorithms.h chain also keeps its class names away from the
|
||||
// pre-2026 drivers that define their own copies.
|
||||
@@ -0,0 +1,94 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./Grid/algorithms/multigrid/MultiGridIO.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution
|
||||
directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Multigrid setup I/O shared by the PVdagM and HDCG chains: a set of
|
||||
// bare-vector scidac records (a near-null basis, or a set of
|
||||
// eigenvectors with their eigenvalues beside them in XML).
|
||||
//
|
||||
// The coarse OPERATOR is not written: coarsening is cheap relative
|
||||
// to producing the basis, and is redone.
|
||||
//
|
||||
// `control` is the scidac binary-IO control word. The library default
|
||||
// is lexicographic + aggregated; pass 0 to read files written by the
|
||||
// pre-2026 HDCG drivers, which used control 0.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
static const int MultiGridIODefaultControl = BinaryIO::BINARYIO_LEXICOGRAPHIC|BinaryIO::BINARYIO_AGGREGATE;
|
||||
|
||||
template <class Field>
|
||||
void saveSubspace(std::vector<Field> &subspace, std::string const fname,
|
||||
int control = MultiGridIODefaultControl)
|
||||
{
|
||||
#ifdef HAVE_LIME
|
||||
Grid::emptyUserRecord record;
|
||||
Grid::ScidacWriter SW(subspace[0].Grid()->IsBoss());
|
||||
SW.open(fname);
|
||||
for (int k = 0; k < (int)subspace.size(); k++) {
|
||||
SW.writeScidacFieldRecord(subspace[k], record, 0, control);
|
||||
}
|
||||
SW.close();
|
||||
#endif
|
||||
}
|
||||
template <class Field>
|
||||
void loadSubspace(std::vector<Field> &subspace, std::string const fname,
|
||||
int control = MultiGridIODefaultControl)
|
||||
{
|
||||
#ifdef HAVE_LIME
|
||||
Grid::emptyUserRecord record;
|
||||
Grid::ScidacReader SR;
|
||||
SR.open(fname);
|
||||
for (int k = 0; k < (int)subspace.size(); k++) {
|
||||
SR.readScidacFieldRecord(subspace[k], record, control);
|
||||
}
|
||||
SR.close();
|
||||
#endif
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Eigenpairs: the vectors as a subspace file, the values in
|
||||
// <fname>.evals.xml. evec.size() fixes how many are read.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
template <class Field>
|
||||
void saveEigenpairs(std::vector<Field> &evec, std::vector<RealD> &eval, std::string const fname,
|
||||
int control = MultiGridIODefaultControl)
|
||||
{
|
||||
GRID_ASSERT(evec.size()==eval.size());
|
||||
saveSubspace(evec, fname, control);
|
||||
if ( evec[0].Grid()->IsBoss() ) {
|
||||
XmlWriter WR(fname+".evals.xml");
|
||||
write(WR,"evals",eval);
|
||||
}
|
||||
}
|
||||
// The eigenvalue file sets the count; the vectors are allocated on grid.
|
||||
template <class Field>
|
||||
void loadEigenpairs(std::vector<Field> &evec, std::vector<RealD> &eval, std::string const fname,
|
||||
GridBase *grid, int control = MultiGridIODefaultControl)
|
||||
{
|
||||
XmlReader RD(fname+".evals.xml");
|
||||
read(RD,"evals",eval);
|
||||
evec.clear();
|
||||
evec.resize(eval.size(), Field(grid));
|
||||
loadSubspace(evec, fname, control);
|
||||
}
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,682 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./Grid/algorithms/multigrid/PVdagMMultiGrid.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution
|
||||
directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
#include <Grid/algorithms/multigrid/DenseCoarseMatrix.h>
|
||||
#include <Grid/algorithms/multigrid/MultiGridIO.h>
|
||||
#include <Grid/algorithms/multigrid/PVdagMOperators.h>
|
||||
#include <Grid/algorithms/multigrid/MrhsMultiGrid.h>
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// The three-level mrhs PVdagM multigrid, as objects:
|
||||
//
|
||||
// MGCoarseGrids the derived coarse grids (owned here, since
|
||||
// conformability is pointer identity and someone
|
||||
// must hold them; declare it BEFORE anything that
|
||||
// borrows from it)
|
||||
// PVdagMMultiGridCoarsening ALL the coarsening information for one gauge
|
||||
// configuration: the raw near-null basis, the
|
||||
// transfer operators it induces, the Galerkin
|
||||
// coarse operator at each level, and the dense
|
||||
// bottom inverse. Everything here is a function
|
||||
// of the gauge field -- the state HMC must
|
||||
// rebuild or maintain as U evolves -- and
|
||||
// everything is solver-independent.
|
||||
// PVdagMMultiGridSolver the solve chain composed on a borrowed
|
||||
// coarsening: smoothers, coarse Krylov, V-cycle,
|
||||
// outer mrhs PGCR.
|
||||
//
|
||||
// Scope discipline: grids outlive the coarsening outlives the solver.
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Subspace I/O is in MultiGridIO.h (shared with the HDCG chain).
|
||||
// Loaded vectors are RAW -- deliberately NOT re-orthogonalised:
|
||||
// CoarsenOperator block orthonormalises in place, and projecting a
|
||||
// block-orthonormal vector onto its own block-orthonormalised
|
||||
// aggregation gives e_k with the near-null content silently gone.
|
||||
// GramGuard below catches that.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// ||<v|v> - I||_F over a set of coarse vectors. Small means the raw
|
||||
// near-null content survived the projection.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
template<class CoarseField>
|
||||
RealD GramDefect(std::vector<CoarseField> &v)
|
||||
{
|
||||
RealD s2=0.0;
|
||||
for(int i=0;i<(int)v.size();i++){
|
||||
for(int j=0;j<(int)v.size();j++){
|
||||
ComplexD sij=TensorRemove(innerProduct(v[i],v[j]));
|
||||
ComplexD d=sij-(i==j?ComplexD(1.0):ComplexD(0.0));
|
||||
s2+=real(d)*real(d)+imag(d)*imag(d);
|
||||
}
|
||||
}
|
||||
return std::sqrt(s2);
|
||||
}
|
||||
|
||||
// On a leak every image collapses to the block unit e_k, the Gram becomes
|
||||
// N*I, and the defect lands at (N-1)*sqrt(nbasis) -- orders above the ~0.2
|
||||
// of a content-preserving projection. Trip well below that so a mis-set
|
||||
// threshold costs a log line rather than the run.
|
||||
template<class CoarseField>
|
||||
void GramGuard(const std::string &name,std::vector<CoarseField> &v,GridBase *grid)
|
||||
{
|
||||
RealD defect = GramDefect(v);
|
||||
RealD N = (RealD)grid->gSites();
|
||||
RealD leak = (N-1.0)*std::sqrt((RealD)v.size());
|
||||
RealD trip = std::sqrt(N);
|
||||
std::cout << GridLogMessage << "GUARD: ||<"<<name<<"|"<<name<<"> - I||_F = " << defect
|
||||
<< " (e_k leak would be " << leak << ", trip at " << trip << ")" << std::endl;
|
||||
GRID_ASSERT( defect < trip );
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// The derived grids of the three-level chain, owned in one place.
|
||||
// The coarse space is UNVECTORISED (sComplex scalar, simd {1,..,1});
|
||||
// the 5D/6D grids are built directly so the SIMD layout is ours.
|
||||
// rhs/batch is dim 0 of the 6D grids, undistributed -- no divisibility
|
||||
// constraint on nrhs.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
class MGCoarseGrids {
|
||||
public:
|
||||
GridCartesian *FGrid; // borrowed
|
||||
int Ls;
|
||||
int batch;
|
||||
Coordinate clatt; // 4d coarse lattice
|
||||
Coordinate cclatt; // 4d coarse-coarse lattice
|
||||
Coordinate c5simd, c5mpi; // 5D coarse simd/mpi
|
||||
Coordinate cmsimd, cmmpi; // 6D coarse simd/mpi
|
||||
// owned:
|
||||
GridCartesian *Coarse5d;
|
||||
GridCartesian *CoarseBatch; // 6D at the coarsening batch
|
||||
GridCartesian *CoarseCoarse5d;
|
||||
GridCartesian *CoarseCoarseBatch;
|
||||
|
||||
MGCoarseGrids(GridCartesian *_FGrid, const MGSetupParams &P)
|
||||
: FGrid(_FGrid)
|
||||
{
|
||||
Coordinate fdims = FGrid->FullDimensions(); // {Ls, x,y,z,t}
|
||||
Coordinate fmpi = FGrid->_processors;
|
||||
GRID_ASSERT( fdims.size() == 5 );
|
||||
Ls = fdims[0];
|
||||
batch = P.CoarsenBatch;
|
||||
|
||||
clatt.resize(4); cclatt.resize(4);
|
||||
for(int d=0;d<4;d++){
|
||||
GRID_ASSERT( fdims[d+1] % P.Block1[d] == 0 );
|
||||
clatt[d] = fdims[d+1] / P.Block1[d];
|
||||
}
|
||||
for(int d=0;d<4;d++){
|
||||
GRID_ASSERT( clatt[d] % P.Block2[d] == 0 );
|
||||
cclatt[d] = clatt[d] / P.Block2[d];
|
||||
}
|
||||
std::cout << GridLogMessage << "MGCoarseGrids: Block1 " << P.Block1 << " coarse lattice " << clatt << std::endl;
|
||||
std::cout << GridLogMessage << "MGCoarseGrids: Block2 " << P.Block2 << " coarse-coarse lattice " << cclatt << std::endl;
|
||||
|
||||
Coordinate c5latt({1,clatt[0],clatt[1],clatt[2],clatt[3]});
|
||||
c5simd = Coordinate({1,1,1,1,1});
|
||||
c5mpi = Coordinate({1,fmpi[1],fmpi[2],fmpi[3],fmpi[4]});
|
||||
Coarse5d = new GridCartesian(c5latt,c5simd,c5mpi);
|
||||
|
||||
cmsimd = Coordinate({1,1,1,1,1,1});
|
||||
cmmpi = Coordinate({1,1,fmpi[1],fmpi[2],fmpi[3],fmpi[4]});
|
||||
Coordinate cblatt({batch,1,clatt[0],clatt[1],clatt[2],clatt[3]});
|
||||
CoarseBatch = new GridCartesian(cblatt,cmsimd,cmmpi);
|
||||
|
||||
Coordinate cc5latt({1,cclatt[0],cclatt[1],cclatt[2],cclatt[3]});
|
||||
CoarseCoarse5d = new GridCartesian(cc5latt,c5simd,c5mpi);
|
||||
|
||||
Coordinate ccblatt({batch,1,cclatt[0],cclatt[1],cclatt[2],cclatt[3]});
|
||||
CoarseCoarseBatch = new GridCartesian(ccblatt,cmsimd,cmmpi);
|
||||
}
|
||||
~MGCoarseGrids()
|
||||
{
|
||||
delete CoarseCoarseBatch;
|
||||
delete CoarseCoarse5d;
|
||||
delete CoarseBatch;
|
||||
delete Coarse5d;
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// The fp32 fine grids: same lattice and decomposition as the fp64 fine
|
||||
// grid, vComplexF SIMD layout. For the fp32 fine level inside the
|
||||
// preconditioner (the fp32 fermion operators and the fp32 transfer
|
||||
// operator borrow these). Owned here; declare BEFORE the coarsening.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
class MGFineGridsF {
|
||||
public:
|
||||
GridCartesian *UGridF;
|
||||
GridRedBlackCartesian *UrbGridF;
|
||||
GridCartesian *FGridF;
|
||||
GridRedBlackCartesian *FrbGridF;
|
||||
|
||||
MGFineGridsF(GridCartesian *FGrid)
|
||||
{
|
||||
Coordinate fdims = FGrid->FullDimensions(); // {Ls, x,y,z,t}
|
||||
Coordinate fmpi = FGrid->_processors;
|
||||
GRID_ASSERT( fdims.size() == 5 );
|
||||
int Ls = fdims[0];
|
||||
Coordinate latt4({fdims[1],fdims[2],fdims[3],fdims[4]});
|
||||
Coordinate mpi4 ({fmpi[1], fmpi[2], fmpi[3], fmpi[4]});
|
||||
UGridF = SpaceTimeGrid::makeFourDimGrid(latt4,GridDefaultSimd(Nd,vComplexF::Nsimd()),mpi4);
|
||||
UrbGridF = SpaceTimeGrid::makeFourDimRedBlackGrid(UGridF);
|
||||
FGridF = SpaceTimeGrid::makeFiveDimGrid(Ls,UGridF);
|
||||
FrbGridF = SpaceTimeGrid::makeFiveDimRedBlackGrid(Ls,UGridF);
|
||||
}
|
||||
~MGFineGridsF()
|
||||
{
|
||||
delete FrbGridF;
|
||||
delete FGridF;
|
||||
delete UrbGridF;
|
||||
delete UGridF;
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// ALL the coarsening information for one gauge configuration.
|
||||
//
|
||||
// Owns: the RAW near-null bases (fine and coarse -- retained so the
|
||||
// coarse operators can be REBUILT on a changed gauge field with a
|
||||
// fixed basis, the cheap HMC maintenance step; DiscardBasis() frees
|
||||
// them for valence use), the two coarsened operators, the two block
|
||||
// projectors, the dense bottom inverse (raw owning pointer: null in
|
||||
// the constructor, allocated by BuildDenseBottom, deleted here), and
|
||||
// the transient per-Nrhs solve grids (created by SetNrhs, which owns
|
||||
// the ReleaseGrid -> delete -> new -> SetGrid ordering in ONE place).
|
||||
//
|
||||
// Borrows: the grid bundle. Declare MGCoarseGrids first.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
template<class Fobj,class CComplex,int nbasis>
|
||||
class PVdagMMultiGridCoarsening {
|
||||
public:
|
||||
typedef Lattice<Fobj> FineField;
|
||||
typedef MultiGeneralCoarsenedOperator<Fobj,CComplex,nbasis> CoarseOperator;
|
||||
typedef typename CoarseOperator::CoarseVector CoarseVector;
|
||||
typedef typename CoarseVector::vector_object CoarseSiteObj;
|
||||
// Every coarse level carries the same site type iVector<CComplex,nbasis>:
|
||||
// the coefficient scalar does not deepen with the level (the operator keeps
|
||||
// its own deeper scratch type for the block inner products). Levels are
|
||||
// told apart by their grids, not their C++ types.
|
||||
typedef MultiGeneralCoarsenedOperator<CoarseSiteObj,CComplex,nbasis> CoarseCoarseOperator;
|
||||
typedef typename CoarseCoarseOperator::CoarseVector CoarseCoarseVector;
|
||||
typedef DenseCoarseMatrix<CComplex,nbasis> DenseBottom;
|
||||
// The fp32 fine field, derived from the fp64 one: the fp32 fine level of
|
||||
// the preconditioner projects through the SAME basis in fp32 layout.
|
||||
typedef typename GridTypeMapper<Fobj>::SinglePrecision FobjF;
|
||||
typedef Lattice<FobjF> FineFieldF;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// ONE fine transfer operator. Its STORE follows the coarse sector --
|
||||
// the coarse space is what it feeds -- while its import and export
|
||||
// accept EITHER fine precision, because they are already a layout
|
||||
// transformation and a scalar conversion inside one costs nothing.
|
||||
// So an fp64 outer level and an fp32 V-cycle share this single object,
|
||||
// and no second basis store, handover or retained basis is needed.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
static const bool CoarseIsSingle =
|
||||
( sizeof(typename GridTypeMapper<CComplex>::scalar_type) == sizeof(ComplexF) );
|
||||
typedef typename std::conditional<CoarseIsSingle,FineFieldF,FineField>::type ProjectorField;
|
||||
typedef MultiRHSBlockProject<ProjectorField> ProjectorL1_t;
|
||||
typedef MultiRHSBlockProject<CoarseVector> ProjectorL2_t;
|
||||
|
||||
MGCoarseGrids &Grids; // borrowed
|
||||
MGFineGridsF &GridsF; // borrowed
|
||||
MGSetupParams Params;
|
||||
NextToNearestStencilGeometry5D geom;
|
||||
NextToNearestStencilGeometry5D geom2;
|
||||
CoarseOperator CoarseOpPV;
|
||||
CoarseCoarseOperator CoarseOpL2;
|
||||
ProjectorL1_t MrhsProjectorL1; // store follows the coarse sector
|
||||
ProjectorL2_t MrhsProjectorL2;
|
||||
DenseBottom *DenseCC;
|
||||
std::vector<FineField> rawNull; // RAW fine near-null basis
|
||||
std::vector<CoarseVector> rawPsi; // RAW coarse near-null basis
|
||||
GridCartesian *CMrhs; // transient solve grids, owned
|
||||
GridCartesian *CCMrhs;
|
||||
int nrhs;
|
||||
|
||||
PVdagMMultiGridCoarsening(MGCoarseGrids &_Grids, MGFineGridsF &_GridsF, const MGSetupParams &P)
|
||||
: Grids(_Grids),
|
||||
GridsF(_GridsF),
|
||||
Params(P),
|
||||
geom (_Grids.Coarse5d),
|
||||
geom2(_Grids.CoarseCoarse5d),
|
||||
CoarseOpPV(geom ,_Grids.Coarse5d),
|
||||
CoarseOpL2(geom2,_Grids.CoarseCoarse5d),
|
||||
DenseCC(nullptr),
|
||||
CMrhs(nullptr),
|
||||
CCMrhs(nullptr),
|
||||
nrhs(-1)
|
||||
{};
|
||||
|
||||
~PVdagMMultiGridCoarsening()
|
||||
{
|
||||
if ( DenseCC ) delete DenseCC;
|
||||
CoarseOpPV.ReleaseGrid(); // borrowers let go before their grids die
|
||||
CoarseOpL2.ReleaseGrid();
|
||||
if ( CMrhs ) delete CMrhs;
|
||||
if ( CCMrhs ) delete CCMrhs;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// The RAW fine basis: load from Params.SubspaceFile if it exists,
|
||||
// else GCR inverse iteration from noise (and save if a file name
|
||||
// was given). The Aggregation is scaffolding for CreateSubspaceGCR
|
||||
// only -- that runs entirely on the fine grid, so the coarse grid
|
||||
// it holds is never dereferenced.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void GetSubspace(GridParallelRNG &RNG, LinearOperatorBase<FineField> &FineOp)
|
||||
{
|
||||
uint64_t file_exists=0;
|
||||
if ( Params.SubspaceFile.length() ) {
|
||||
if ( Grids.FGrid->IsBoss() ){ std::ifstream f(Params.SubspaceFile); file_exists=f.good()?1:0; }
|
||||
Grids.FGrid->GlobalSum(file_exists);
|
||||
}
|
||||
rawNull.clear();
|
||||
rawNull.reserve(nbasis);
|
||||
for(int k=0;k<nbasis;k++) rawNull.push_back(FineField(Grids.FGrid));
|
||||
if ( file_exists ){
|
||||
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening: loading subspace "
|
||||
<< Params.SubspaceFile << " (kept RAW)" << std::endl;
|
||||
loadSubspace(rawNull, Params.SubspaceFile);
|
||||
} else {
|
||||
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening: GCR subspace generation" << std::endl;
|
||||
Aggregation<Fobj,CComplex,nbasis> Agg(Grids.Coarse5d,Grids.FGrid,0);
|
||||
Agg.CreateSubspaceGCR(RNG,FineOp,nbasis);
|
||||
for(int k=0;k<nbasis;k++)
|
||||
rawNull[k]=Agg.subspace[k];
|
||||
if ( Params.SubspaceFile.length() )
|
||||
saveSubspace(rawNull, Params.SubspaceFile);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Both coarsenings from the retained RAW basis. Calling this again
|
||||
// on a CHANGED fine operator is the fixed-basis rebuild: for the
|
||||
// PVdagM coarsening the coarse operator with a fixed basis is exact
|
||||
// in the changed links. CoarsenOperator block-orthonormalises its
|
||||
// input in place, so working copies are taken and the RAW vectors
|
||||
// survive for the next rebuild.
|
||||
//
|
||||
// The fine operator must be the PVdagM wrapper (it needs
|
||||
// SloppyComms: the coarsening builds the PRECONDITIONER, so its
|
||||
// halos follow the FineSloppyComms policy, restored EXACT on exit).
|
||||
////////////////////////////////////////////////////////////////////
|
||||
template<class PVdagMOp>
|
||||
void Coarsen(PVdagMOp &FineOp, int retain_basis=0)
|
||||
{
|
||||
GRID_ASSERT( rawNull.size() == nbasis );
|
||||
|
||||
// L1: working copy of the raw fine basis, orthonormalised in place
|
||||
std::vector<FineField> sub(rawNull);
|
||||
|
||||
CoarseOpPV.SetGrid(Grids.CoarseBatch);
|
||||
|
||||
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening: L1 CoarsenOperator, batch "
|
||||
<< Grids.batch << std::endl;
|
||||
|
||||
// CoarsenOperator orthonormalises sub in place and imports it into OUR
|
||||
// transfer operator, which it then uses. One basis store, imported once.
|
||||
// The projector's grid carries the STORE's SIMD layout, which is the fp32
|
||||
// fine grid when the coarse sector is fp32; fp64 vectors convert on import.
|
||||
MrhsProjectorL1.Allocate(nbasis,
|
||||
CoarseIsSingle ? (GridBase *)GridsF.FGridF : (GridBase *)Grids.FGrid,
|
||||
Grids.Coarse5d);
|
||||
FineOp.SloppyComms(Params.FineSloppyComms);
|
||||
CoarseOpPV.CoarsenOperator(FineOp,sub,Grids.Coarse5d,Grids.batch,MrhsProjectorL1);
|
||||
FineOp.SloppyComms(0);
|
||||
// The Lattice copy of the basis has served its purpose: the transfer
|
||||
// operator holds it in BLAS layout from here on.
|
||||
sub.clear(); sub.shrink_to_fit();
|
||||
|
||||
std::vector<CoarseVector> psi(nbasis,Grids.Coarse5d);
|
||||
MrhsProjectorL1.blockProject(rawNull,psi);
|
||||
GramGuard("psi_coarse",psi,Grids.Coarse5d);
|
||||
|
||||
rawPsi.clear();
|
||||
rawPsi.reserve(nbasis);
|
||||
for(int k=0;k<nbasis;k++) rawPsi.push_back(psi[k]);
|
||||
|
||||
// L2: the L1 operator is natively multiRHS at the batch grid
|
||||
CoarseOpL2.SetGrid(Grids.CoarseCoarseBatch);
|
||||
NonHermitianLinearOperator<CoarseOperator,CoarseVector> LinOpCoarse(CoarseOpPV);
|
||||
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening: L2 CoarsenOperator, batch "
|
||||
<< Grids.batch << std::endl;
|
||||
MrhsProjectorL2.Allocate(nbasis,Grids.Coarse5d,Grids.CoarseCoarse5d);
|
||||
CoarseOpL2.CoarsenOperator(LinOpCoarse,Grids.CoarseBatch,psi,Grids.CoarseCoarse5d,MrhsProjectorL2);
|
||||
{
|
||||
std::vector<CoarseCoarseVector> psi_cc(nbasis,Grids.CoarseCoarse5d);
|
||||
MrhsProjectorL2.blockProject(rawPsi,psi_cc); // RAW vectors in
|
||||
GramGuard("psi_cc",psi_cc,Grids.CoarseCoarse5d);
|
||||
}
|
||||
// The RAW basis has done its work (the L2 null space above was its last
|
||||
// use) and is the single largest resident block of the setup, so it is
|
||||
// freed. retain_basis keeps it for a caller that coarsens again from the
|
||||
// same basis; Setup.RetainSubspace keeps it for the object's whole life,
|
||||
// which is what a fixed-basis rebuild on a changed gauge field (HMC)
|
||||
// needs. A valence solve never rebuilds.
|
||||
if ( !retain_basis && !Params.RetainSubspace ) DiscardBasis();
|
||||
MrhsProjectorL1.ReleaseScratch();
|
||||
MrhsProjectorL2.ReleaseScratch();
|
||||
|
||||
nrhs = -1; // both operators left at the batch grids
|
||||
ReportDeviceFootprint("after Coarsen");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Dense bottom: import the L2 matrix elements, invert (2D
|
||||
// block-cyclic, fp64), then the check Import cannot run itself --
|
||||
// the mrhs operator applies only on a D+1 grid, so drive it at
|
||||
// Nrhs 1 through a slice: ||A Ainv x - x||/||x||.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void BuildDenseBottom(void)
|
||||
{
|
||||
if ( DenseCC ) delete DenseCC;
|
||||
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening: L3 dense bottom import" << std::endl;
|
||||
DenseCC = new DenseBottom(Grids.CoarseCoarse5d);
|
||||
DenseCC->Import(CoarseOpL2);
|
||||
|
||||
Coordinate cc1latt({1,1,Grids.cclatt[0],Grids.cclatt[1],Grids.cclatt[2],Grids.cclatt[3]});
|
||||
GridCartesian *CoarseCoarseOne = new GridCartesian(cc1latt,Grids.cmsimd,Grids.cmmpi);
|
||||
CoarseOpL2.SetGrid(CoarseCoarseOne);
|
||||
|
||||
CoarseCoarseVector x(Grids.CoarseCoarse5d),y(Grids.CoarseCoarse5d),z(Grids.CoarseCoarse5d);
|
||||
GridParallelRNG dRNG(Grids.CoarseCoarse5d); dRNG.SeedFixedIntegers(std::vector<int>({11,12,13,14}));
|
||||
random(dRNG,x);
|
||||
|
||||
(*DenseCC)(x,y); // y = Ainv x
|
||||
|
||||
CoarseCoarseVector y1(CoarseCoarseOne),z1(CoarseCoarseOne);
|
||||
InsertSliceFast(y,y1,0,0);
|
||||
CoarseOpL2.M(y1,z1); // z = A y
|
||||
ExtractSliceFast(z,z1,0,0);
|
||||
|
||||
z = z - x;
|
||||
RealD rel = std::sqrt(norm2(z)/norm2(x));
|
||||
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening: L3 dense ||A Ainv x - x||/||x|| = "
|
||||
<< rel << std::endl;
|
||||
GRID_ASSERT( rel < 1.0e-2 );
|
||||
|
||||
CoarseOpL2.ReleaseGrid(); // let go before the grid dies
|
||||
delete CoarseCoarseOne;
|
||||
nrhs = -1;
|
||||
ReportDeviceFootprint("after BuildDenseBottom");
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Retarget both operators to a solve Nrhs. The matrix elements are
|
||||
// Nrhs independent and survive; only the D+1 grids and BLAS buffers
|
||||
// change. The ordering discipline (borrowers release BEFORE their
|
||||
// grids are destroyed) lives here and nowhere else.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void SetNrhs(int nr)
|
||||
{
|
||||
if ( nr == nrhs ) return;
|
||||
CoarseOpPV.ReleaseGrid();
|
||||
CoarseOpL2.ReleaseGrid();
|
||||
if ( CMrhs ) { delete CMrhs; CMrhs =nullptr; }
|
||||
if ( CCMrhs ) { delete CCMrhs; CCMrhs=nullptr; }
|
||||
Coordinate cml ({nr,1,Grids.clatt[0], Grids.clatt[1], Grids.clatt[2], Grids.clatt[3]});
|
||||
Coordinate ccml({nr,1,Grids.cclatt[0],Grids.cclatt[1],Grids.cclatt[2],Grids.cclatt[3]});
|
||||
CMrhs = new GridCartesian(cml, Grids.cmsimd,Grids.cmmpi);
|
||||
CCMrhs = new GridCartesian(ccml,Grids.cmsimd,Grids.cmmpi);
|
||||
CoarseOpPV.SetGrid(CMrhs);
|
||||
CoarseOpL2.SetGrid(CCMrhs);
|
||||
nrhs = nr;
|
||||
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening: operators at Nrhs " << nr << std::endl;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Galerkin certificate for the L1 coarsening: ||A_c x - P^dag A P x||
|
||||
// over ||A_c x|| on a random coarse vector, through the SAME transfer
|
||||
// operators the solve uses. The coarse operator is compared with its
|
||||
// own definition, so this reads the ROUNDING level of the coarsening
|
||||
// (fp64 coarse ~1e-13, fp32 coarse ~1e-6) independently of how the
|
||||
// solve converges. Drives the fine operator with EXACT halos; at more
|
||||
// than one rank the sloppy-halo coarsening error is included in the
|
||||
// reading. Nrhs 1; the caller's Nrhs is restored by its own SetNrhs.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
template<class PVdagMOp>
|
||||
RealD CertifyCoarsening(PVdagMOp &FineOp)
|
||||
{
|
||||
SetNrhs(1);
|
||||
CoarseVector xc(CMrhs), Acx(CMrhs), PtAPx(CMrhs);
|
||||
GridParallelRNG cRNG(CMrhs); cRNG.SeedFixedIntegers(std::vector<int>({21,22,23,24}));
|
||||
random(cRNG,xc);
|
||||
|
||||
CoarseOpPV.M(xc,Acx); // A_c x
|
||||
|
||||
std::vector<FineField> Px(1,Grids.FGrid), APx(1,Grids.FGrid);
|
||||
MrhsProjectorL1.blockPromote(Px,xc); // P x
|
||||
FineOp.SloppyComms(0);
|
||||
FineOp.Op(Px[0],APx[0]); // A P x
|
||||
MrhsProjectorL1.blockProject(APx,PtAPx); // P^dag A P x
|
||||
|
||||
CoarseVector d(CMrhs); d = Acx - PtAPx;
|
||||
RealD rel = std::sqrt(norm2(d)/norm2(Acx));
|
||||
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening: L1 GALERKIN CERTIFICATE "
|
||||
<< "||A_c x - P^dag A P x||/||A_c x|| = " << rel << std::endl;
|
||||
return rel;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// The device memory this coarsening holds that the memory manager
|
||||
// CANNOT evict: BLAS stores, which are plain device allocations.
|
||||
// Lattice fields are omitted on purpose -- they are evictable, so they
|
||||
// cost eviction traffic, not failure. What is reported is what has to
|
||||
// fit alongside the manager's cache, the comms buffers and the shm
|
||||
// segment, which is the budget an out-of-memory run actually breaks.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void ReportDeviceFootprint(const std::string &when)
|
||||
{
|
||||
uint64_t p1 = MrhsProjectorL1.DeviceBytes();
|
||||
uint64_t p2 = MrhsProjectorL2.DeviceBytes();
|
||||
uint64_t o1 = CoarseOpPV.DeviceBytes();
|
||||
uint64_t o2 = CoarseOpL2.DeviceBytes();
|
||||
uint64_t dn = DenseCC ? DenseCC->DeviceBytes() : 0;
|
||||
uint64_t tot = p1+p2+o1+o2+dn;
|
||||
const double G = 1024.*1024.*1024.;
|
||||
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening (" << when << "): resident device memory/rank "
|
||||
<< tot/G << " GB = transfer L1 " << p1/G << " + L2 " << p2/G
|
||||
<< " + operator L1 " << o1/G << " + L2 " << o2/G
|
||||
<< " + dense " << dn/G << " GB" << std::endl;
|
||||
std::cout << GridLogMessage << "PVdagMMultiGridCoarsening (" << when << "): not evictable; must fit alongside the "
|
||||
<< MemoryManager::DeviceMaxBytes/G
|
||||
<< " GB manager cache, the comms buffers and the shm segment" << std::endl;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Free the retained RAW bases (valence use: coarsening is final for
|
||||
// this configuration and the fine basis is ~GB-scale host memory).
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void DiscardBasis(void)
|
||||
{
|
||||
rawNull.clear(); rawNull.shrink_to_fit();
|
||||
rawPsi.clear(); rawPsi.shrink_to_fit();
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// The solve chain on a borrowed coarsening: adaptive PGCR smoothers
|
||||
// at both levels (the one correct route for this non-Hermitian
|
||||
// chain), dense bottom, mrhs V-cycle, outer shared-coefficient mrhs
|
||||
// PGCR. Construction retargets the coarsening to this Nrhs.
|
||||
//
|
||||
// Halo policy: the whole V-cycle is preconditioner and runs with
|
||||
// sloppy halos when FineSloppyComms is set; the outer Krylov is EXACT
|
||||
// (its applications define what "converged" means), asserted by the
|
||||
// FINAL true-residual report in Solve.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
template<class Matrix,class MatrixF,class Coarsening>
|
||||
class PVdagMMultiGridSolver {
|
||||
public:
|
||||
typedef typename Coarsening::FineField FineField;
|
||||
typedef typename Coarsening::FineFieldF FineFieldF;
|
||||
typedef typename Coarsening::CoarseOperator CoarseOperator;
|
||||
typedef typename Coarsening::CoarseVector CoarseVector;
|
||||
typedef typename Coarsening::CoarseCoarseOperator CoarseCoarseOperator;
|
||||
typedef typename Coarsening::CoarseCoarseVector CoarseCoarseVector;
|
||||
typedef typename Coarsening::DenseBottom DenseBottom;
|
||||
typedef PrecGeneralisedConjugateResidualNonHermitian<FineField> FineSmoother_t;
|
||||
typedef PrecGeneralisedConjugateResidualNonHermitian<FineFieldF> FineSmootherF_t;
|
||||
typedef PrecGeneralisedConjugateResidualNonHermitian<CoarseVector> CoarseKrylov_t;
|
||||
|
||||
Coarsening &C;
|
||||
PVdagMMultiGridParams Params;
|
||||
int nrhs;
|
||||
int _regrid; // FIRST member-like init: SetNrhs before members capture grids
|
||||
|
||||
PVdagMLinearOperator<Matrix,FineField> PVdagM;
|
||||
ShiftedPVdagMLinearOperator<Matrix,FineField> ShiftedPVdagM;
|
||||
TrivialPrecon<FineField> simple_fine;
|
||||
TrivialPrecon<CoarseVector> simpleC;
|
||||
NonHermitianLinearOperator<CoarseOperator,CoarseVector> LinOpC;
|
||||
ShiftedLinearOperator<CoarseVector> ShiftedC;
|
||||
MrhsDenseCCSolve<DenseBottom,CoarseCoarseVector> ccSolve;
|
||||
CoarseKrylov_t CoarseSmootherGCR;
|
||||
MrhsCoarseThreeLevelPrec<CoarseVector,CoarseCoarseVector> L2to3Precon;
|
||||
CoarseKrylov_t L2PGCR;
|
||||
// The fp64 fine level of the preconditioner
|
||||
FineSmoother_t SmootherGCR;
|
||||
MrhsTwoLevelMG<FineField,CoarseVector,FineSmoother_t,typename Coarsening::ProjectorL1_t> ThreeLevelPrecon;
|
||||
// The fp32 fine level of the preconditioner, behind the fp64/fp32 seam.
|
||||
// Both share the coarse chain (L2PGCR); the outer Krylov picks one.
|
||||
PVdagMLinearOperator<MatrixF,FineFieldF> PVdagMF;
|
||||
ShiftedPVdagMLinearOperator<MatrixF,FineFieldF> ShiftedPVdagMF;
|
||||
TrivialPrecon<FineFieldF> simple_fine_f;
|
||||
FineSmootherF_t SmootherGCRF;
|
||||
MrhsTwoLevelMG<FineFieldF,CoarseVector,FineSmootherF_t,typename Coarsening::ProjectorL1_t> ThreeLevelPreconF;
|
||||
MrhsMixedPrecPreconditioner<FineField,FineFieldF> PreconSeam;
|
||||
MrhsPGCRNonHermitian<FineField> L1PGCR;
|
||||
|
||||
PVdagMMultiGridSolver(Matrix &Ddwf, Matrix &Dpv, MatrixF &DdwfF, MatrixF &DpvF,
|
||||
Coarsening &_C, const PVdagMMultiGridParams &P, int nr)
|
||||
: C(_C), Params(P), nrhs(nr),
|
||||
_regrid((C.SetNrhs(nr),0)), // members below capture C.CMrhs/C.CCMrhs
|
||||
PVdagM(Ddwf,Dpv),
|
||||
ShiftedPVdagM(P.FineSmoother.Shift,Ddwf,Dpv),
|
||||
LinOpC(C.CoarseOpPV),
|
||||
ShiftedC(P.CoarseSmoother.Shift,LinOpC),
|
||||
ccSolve(*C.DenseCC,nr),
|
||||
// Coarse smoother: one preconditioned restart of nstep GCR steps.
|
||||
CoarseSmootherGCR(0.01,1,ShiftedC,simpleC,P.CoarseSmoother.Mmax,P.CoarseSmoother.Nstep),
|
||||
L2to3Precon(LinOpC,CoarseSmootherGCR,C.MrhsProjectorL2,ccSolve,
|
||||
C.Grids.Coarse5d,C.Grids.CoarseCoarse5d,C.CCMrhs,nr),
|
||||
// Coarse Krylov: Order/16 restarts of 16 steps.
|
||||
L2PGCR(P.CoarseSolver.Tol,P.CoarseSolver.Order/16,LinOpC,L2to3Precon,P.CoarseSolver.Mmax,16),
|
||||
// Fine smoother: one restart of nstep GCR steps, tolerance 0 = fixed work.
|
||||
SmootherGCR(0.0,1,ShiftedPVdagM,simple_fine,P.FineSmoother.Mmax,P.FineSmoother.Nstep),
|
||||
ThreeLevelPrecon(PVdagM,SmootherGCR,C.MrhsProjectorL1,L2PGCR,C.Grids.Coarse5d,C.CMrhs),
|
||||
PVdagMF(DdwfF,DpvF),
|
||||
ShiftedPVdagMF(P.FineSmoother.Shift,DdwfF,DpvF),
|
||||
SmootherGCRF(0.0,1,ShiftedPVdagMF,simple_fine_f,P.FineSmoother.Mmax,P.FineSmoother.Nstep),
|
||||
ThreeLevelPreconF(PVdagMF,SmootherGCRF,C.MrhsProjectorL1,L2PGCR,C.Grids.Coarse5d,C.CMrhs),
|
||||
PreconSeam(ThreeLevelPreconF,Ddwf.FermionGrid(),DdwfF.FermionGrid(),nr),
|
||||
L1PGCR(P.Outer.Tol,P.Outer.MaxIterations,PVdagM,
|
||||
(P.Setup.FinePrecision==MGPrecision::fp32)
|
||||
? static_cast<MrhsLinearFunction<FineField>&>(PreconSeam)
|
||||
: static_cast<MrhsLinearFunction<FineField>&>(ThreeLevelPrecon),
|
||||
P.Outer.Mmax,P.Outer.Nstep)
|
||||
{
|
||||
GRID_ASSERT( C.DenseCC != nullptr );
|
||||
|
||||
CoarseSmootherGCR.Level(2);
|
||||
CoarseSmootherGCR.Name("Csmoother");
|
||||
CoarseSmootherGCR.SetZeroGuess(1);
|
||||
|
||||
L2PGCR.Level(2);
|
||||
L2PGCR.Name("Couter");
|
||||
L2PGCR.SetZeroGuess(1);
|
||||
|
||||
SmootherGCR.Level(1);
|
||||
SmootherGCR.Name("Fsmoother");
|
||||
SmootherGCR.SetZeroGuess(1);
|
||||
|
||||
SmootherGCRF.Level(1);
|
||||
SmootherGCRF.Name("Fsmoother");
|
||||
SmootherGCRF.SetZeroGuess(1);
|
||||
|
||||
L1PGCR.Level(1);
|
||||
L1PGCR.Name("Fouter");
|
||||
L1PGCR.SetZeroGuess(1);
|
||||
|
||||
// The V-cycle is preconditioner: sloppy halos inside, exact restored on exit.
|
||||
ThreeLevelPrecon.SetSloppy = [this](int s){ PVdagM.SloppyComms(s); };
|
||||
ThreeLevelPrecon.SloppyComms = Params.Setup.FineSloppyComms;
|
||||
ThreeLevelPreconF.SetSloppy = [this](int s){ PVdagMF.SloppyComms(s); };
|
||||
ThreeLevelPreconF.SloppyComms = Params.Setup.FineSloppyComms;
|
||||
PVdagM.SloppyComms(0);
|
||||
PVdagMF.SloppyComms(0);
|
||||
// Three stages, three arithmetics, and a wire format that follows the
|
||||
// ARITHMETIC of the stage it serves: fp32 on an fp64 operator, bf16 on
|
||||
// an fp32 one. The coarsening always applies the fp64 operator, so its
|
||||
// wire is fp32 whatever the preconditioner runs in.
|
||||
const bool sloppy = Params.Setup.FineSloppyComms;
|
||||
const bool pfp32 = (Params.Setup.FinePrecision==MGPrecision::fp32);
|
||||
std::cout << GridLogMessage << "PVdagMMultiGridSolver: Nrhs " << nr << std::endl;
|
||||
std::cout << GridLogMessage << " outer Krylov : fp64, halos EXACT (the true residual is measured here)" << std::endl;
|
||||
std::cout << GridLogMessage << " preconditioner fine: " << (pfp32 ? "fp32 (seam at the outer Krylov)" : "fp64")
|
||||
<< ", halos " << (sloppy ? (pfp32 ? "SLOPPY bf16 wire" : "SLOPPY fp32 wire") : "exact") << std::endl;
|
||||
std::cout << GridLogMessage << " coarsening (done) : fp64 operator, halos "
|
||||
<< (sloppy ? "SLOPPY fp32 wire" : "exact")
|
||||
<< " -- the coarse operator does not follow FinePrecision" << std::endl;
|
||||
}
|
||||
|
||||
void Solve(std::vector<FineField> &src, std::vector<FineField> &sol)
|
||||
{
|
||||
GRID_ASSERT( (int)src.size() == nrhs );
|
||||
// Start the solve from a clean device: evict setup-era Lattice copies
|
||||
// and release the allocation caches' held blocks, so the LRU cap
|
||||
// applies to the solve's own working set (OOM policy, Frontier).
|
||||
MemoryManager::EvictAll();
|
||||
MemoryManager::DropCache();
|
||||
|
||||
GridStopWatch w; w.Start();
|
||||
L1PGCR(src,sol);
|
||||
w.Stop();
|
||||
std::cout << GridLogMessage << "PVdagMMultiGridSolver: Nrhs "<<nrhs<<" total " << w.Elapsed()
|
||||
<< " (per RHS: " << w.useconds()/1.0e6/nrhs << " s)" << std::endl;
|
||||
|
||||
// The outer operator is exact by policy; assert the state rather than
|
||||
// trust it -- a preconditioner that failed to restore would surface here.
|
||||
PVdagM.SloppyComms(0);
|
||||
{
|
||||
FineField Ax(src[0].Grid());
|
||||
RealD worst=0.0;
|
||||
for(int r=0;r<nrhs;r++){
|
||||
PVdagM.Op(sol[r],Ax);
|
||||
Ax=Ax-src[r];
|
||||
RealD rn=std::sqrt(norm2(Ax)/norm2(src[r]));
|
||||
std::cout << GridLogMessage << "FINAL Nrhs "<<nrhs<<": rhs["<<r<<"] true residual = " << rn << std::endl;
|
||||
worst=std::max(worst,rn);
|
||||
}
|
||||
std::cout << GridLogMessage << "FINAL Nrhs "<<nrhs<<": worst-case residual = " << worst
|
||||
<< " (exact-halo verification)" << std::endl;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,165 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./Grid/algorithms/multigrid/PVdagMMultiGridParams.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution
|
||||
directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// Serialisable parameters for the mrhs PVdagM multigrid solver chain.
|
||||
//
|
||||
// Named for the multigrid FORM it configures, as WilsonMGParams is for the
|
||||
// Wilson MG tests; an MrhsHDCGParams will compose the same generic MG*
|
||||
// sub-structs for the Hermitian chain. Read from XML (or JSON -- the
|
||||
// serialisation macros give both) via ReadPVdagMMultiGridParams below and
|
||||
// printed at startup by the macro's operator<<, so every log identifies its
|
||||
// own run. The constructor defaults ARE a tuned operating point, not
|
||||
// arbitrary: an unconfigured run reproduces it, so changing them changes what
|
||||
// an unconfigured run does. Smoother mmax == nstep (full GCR history).
|
||||
//
|
||||
// There are NO environment-variable controls anywhere in this subsystem:
|
||||
// parameters live here, library-internal constants are hard defaults in their
|
||||
// classes, verbosity is the --log channels.
|
||||
// Research instruments (GCR coefficient recording/replay, Chebyshev and
|
||||
// stationary smoother variants, power iteration) are deliberately NOT part
|
||||
// of this interface: they remain programmatic, for algorithmic studies,
|
||||
// not consumer knobs.
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Arithmetic precision of the fine level INSIDE the preconditioner -- the
|
||||
// smoother and the V-cycle's own fine residuals. The outer Krylov is
|
||||
// always fp64: the fp64/fp32 seam sits at its preconditioner call.
|
||||
// Orthogonal to FineSloppyComms, which is the halo WIRE format.
|
||||
GRID_SERIALIZABLE_ENUM(MGPrecision, undef, fp64, 1, fp32, 2);
|
||||
|
||||
// The smoother is the adaptive shifted PGCR -- the one correct route for
|
||||
// this non-Hermitian chain (stationary replay and Chebyshev were explored
|
||||
// and did not win here; Chebyshev remains the HERMITIAN chain's smoother).
|
||||
struct MGSmootherParams : Serializable {
|
||||
GRID_SERIALIZABLE_CLASS_MEMBERS(MGSmootherParams,
|
||||
RealD, Shift,
|
||||
int, Nstep,
|
||||
int, Mmax);
|
||||
MGSmootherParams(RealD shift=0.1, int nstep=6, int mmax=6)
|
||||
: Shift(shift), Nstep(nstep), Mmax(mmax) {};
|
||||
};
|
||||
|
||||
struct MGCoarseSolverParams : Serializable {
|
||||
GRID_SERIALIZABLE_CLASS_MEMBERS(MGCoarseSolverParams,
|
||||
RealD, Tol,
|
||||
int, Order,
|
||||
int, Mmax);
|
||||
MGCoarseSolverParams() : Tol(0.05), Order(200), Mmax(16) {};
|
||||
};
|
||||
|
||||
struct MGOuterParams : Serializable {
|
||||
GRID_SERIALIZABLE_CLASS_MEMBERS(MGOuterParams,
|
||||
RealD, Tol,
|
||||
int, MaxIterations,
|
||||
int, Mmax,
|
||||
int, Nstep);
|
||||
MGOuterParams() : Tol(1.0e-8), MaxIterations(1000), Mmax(4), Nstep(8) {};
|
||||
};
|
||||
|
||||
struct MGDenseParams : Serializable {
|
||||
GRID_SERIALIZABLE_CLASS_MEMBERS(MGDenseParams,
|
||||
int, LeafSpan); // big-leaf span in blocks; see BlockCyclicSchurInverse
|
||||
MGDenseParams() : LeafSpan(9) {};
|
||||
};
|
||||
|
||||
struct MGSetupParams : Serializable {
|
||||
GRID_SERIALIZABLE_CLASS_MEMBERS(MGSetupParams,
|
||||
std::vector<int>, Block1, // fine -> coarse blocking
|
||||
std::vector<int>, Block2, // coarse -> coarse-coarse blocking
|
||||
int, CoarsenBatch,
|
||||
std::string, SubspaceFile, // scidac; empty = create from noise, no I/O
|
||||
int, FineSloppyComms, // reduced-precision halo WIRE inside the preconditioner only: fp32 on the fp64 operator, bf16 on the fp32 operator
|
||||
MGPrecision, FinePrecision, // fine-level ARITHMETIC inside the preconditioner
|
||||
int, RetainSubspace); // keep the RAW basis after setup (nbasis fine vectors); needed ONLY for a fixed-basis rebuild on a changed gauge field (HMC). A valence solve never rebuilds, so 0 frees it.
|
||||
MGSetupParams()
|
||||
: Block1({2,2,3,3}), Block2({4,4,2,4}), CoarsenBatch(9),
|
||||
SubspaceFile(""), FineSloppyComms(1), FinePrecision(MGPrecision::fp64),
|
||||
RetainSubspace(0) {};
|
||||
};
|
||||
|
||||
struct PVdagMMultiGridParams : Serializable {
|
||||
GRID_SERIALIZABLE_CLASS_MEMBERS(PVdagMMultiGridParams,
|
||||
MGSetupParams, Setup,
|
||||
MGSmootherParams, FineSmoother,
|
||||
MGSmootherParams, CoarseSmoother,
|
||||
MGCoarseSolverParams, CoarseSolver,
|
||||
MGOuterParams, Outer,
|
||||
MGDenseParams, Dense);
|
||||
PVdagMMultiGridParams()
|
||||
: FineSmoother (0.1, 6, 6),
|
||||
CoarseSmoother(0.1, 2, 2) {};
|
||||
};
|
||||
|
||||
inline void CheckValidity(const PVdagMMultiGridParams &P)
|
||||
{
|
||||
GRID_ASSERT( P.Setup.Block1.size() == 4 );
|
||||
GRID_ASSERT( P.Setup.Block2.size() == 4 );
|
||||
GRID_ASSERT( P.Setup.CoarsenBatch >= 1 );
|
||||
GRID_ASSERT( P.Setup.FinePrecision != MGPrecision::undef );
|
||||
GRID_ASSERT( P.FineSmoother.Nstep > 0 );
|
||||
GRID_ASSERT( P.CoarseSmoother.Nstep > 0 );
|
||||
GRID_ASSERT( P.CoarseSolver.Tol > 0.0 );
|
||||
GRID_ASSERT( P.Outer.Tol > 0.0 );
|
||||
GRID_ASSERT( P.Dense.LeafSpan >= 1 );
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// Read from an XML file; on a MISSING file the boss writes <file>.templ from
|
||||
// the defaults and returns false so the caller can Grid_finalize() and exit
|
||||
// -- the produced template is the documentation of every parameter. An
|
||||
// empty filename means "defaults": no I/O, returns true. Always prints the
|
||||
// effective parameters, so the log describes the run.
|
||||
//
|
||||
// Drivers fetch the filename themselves, e.g.
|
||||
// std::string pfile("");
|
||||
// if( GridCmdOptionExists(argv,argv+argc,"--multigrid-params") )
|
||||
// pfile = GridCmdOptionPayload(argv,argv+argc,"--multigrid-params");
|
||||
// if( !ReadPVdagMMultiGridParams(Params, pfile) ) { Grid_finalize(); return 0; }
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
inline bool ReadPVdagMMultiGridParams(PVdagMMultiGridParams &P, const std::string &xmlfile)
|
||||
{
|
||||
if ( xmlfile.length() ) {
|
||||
bool good;
|
||||
{ std::ifstream f(xmlfile); good = f.good(); }
|
||||
if ( !good ) {
|
||||
std::cout << GridLogMessage << "PVdagMMultiGridParams: " << xmlfile << " does not exist" << std::endl;
|
||||
if ( GlobalSharedMemory::WorldRank == 0 ) {
|
||||
XmlWriter WR(xmlfile+".templ");
|
||||
write(WR, "PVdagMMultiGridParams", P);
|
||||
std::cout << GridLogMessage << "PVdagMMultiGridParams: template written to "
|
||||
<< xmlfile << ".templ" << std::endl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
XmlReader RD(xmlfile);
|
||||
read(RD, "PVdagMMultiGridParams", P);
|
||||
}
|
||||
CheckValidity(P);
|
||||
std::cout << GridLogMessage << "PVdagMMultiGridParams ("
|
||||
<< (xmlfile.length() ? xmlfile : std::string("defaults")) << "):" << std::endl;
|
||||
std::cout << P << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,179 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./Grid/algorithms/multigrid/PVdagMOperators.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution
|
||||
directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// A = PV^dag M (non-Hermitian). The operator the PVdagM multigrid
|
||||
// coarsens and solves.
|
||||
//
|
||||
// SloppyComms delegates to BOTH underlying fermion operators, so one
|
||||
// call controls every fine halo in the chain -- including any shifted
|
||||
// wrapper built on the same two matrices. Halo-precision POLICY:
|
||||
// reduced-precision (fp32 wire) halos belong in the PRECONDITIONER --
|
||||
// the smoother, the V-cycle's own residuals, the coarsening -- and
|
||||
// NEVER in the outer Krylov, whose applications define what
|
||||
// "converged" means. The operators default to EXACT; the V-cycle
|
||||
// turns sloppiness on for its own scope only (MrhsTwoLevelMG).
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
template<class Matrix,class Field>
|
||||
class PVdagMLinearOperator : public LinearOperatorBase<Field> {
|
||||
Matrix &_Mat;
|
||||
Matrix &_PV;
|
||||
public:
|
||||
PVdagMLinearOperator(Matrix &Mat,Matrix &PV):
|
||||
_Mat(Mat),_PV(PV)
|
||||
{};
|
||||
void SloppyComms(int sloppy)
|
||||
{
|
||||
_Mat.SloppyComms(sloppy);
|
||||
_PV.SloppyComms(sloppy);
|
||||
}
|
||||
void OpDiag (const Field &in, Field &out)
|
||||
{
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void OpDir (const Field &in, Field &out,int dir,int disp)
|
||||
{
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void OpDirAll (const Field &in, std::vector<Field> &out)
|
||||
{
|
||||
GRID_ASSERT(0);
|
||||
};
|
||||
void Op (const Field &in, Field &out)
|
||||
{
|
||||
Field tmp(in.Grid());
|
||||
_Mat.M(in,tmp);
|
||||
_PV.Mdag(tmp,out);
|
||||
}
|
||||
void AdjOp (const Field &in, Field &out)
|
||||
{
|
||||
Field tmp(in.Grid());
|
||||
_PV.M(in,tmp);
|
||||
_Mat.Mdag(tmp,out);
|
||||
}
|
||||
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2)
|
||||
{
|
||||
HermOp(in,out);
|
||||
ComplexD d=innerProduct(in,out);
|
||||
n1=real(d);
|
||||
n2=norm2(out);
|
||||
}
|
||||
void HermOp(const Field &in, Field &out)
|
||||
{
|
||||
Field tmp(in.Grid());
|
||||
Op(in,tmp);
|
||||
AdjOp(tmp,out);
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// A + shift, built directly on the two matrices (fine level: avoids
|
||||
// an extra fine-field pass through a generic shifted wrapper).
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
template<class Matrix,class Field>
|
||||
class ShiftedPVdagMLinearOperator : public LinearOperatorBase<Field> {
|
||||
Matrix &_Mat;
|
||||
Matrix &_PV;
|
||||
public:
|
||||
RealD shift;
|
||||
ShiftedPVdagMLinearOperator(RealD _shift,Matrix &Mat,Matrix &PV): shift(_shift),_Mat(Mat),_PV(PV){};
|
||||
void OpDiag (const Field &in, Field &out)
|
||||
{
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void OpDir (const Field &in, Field &out,int dir,int disp)
|
||||
{
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void OpDirAll (const Field &in, std::vector<Field> &out)
|
||||
{
|
||||
GRID_ASSERT(0);
|
||||
};
|
||||
void Op (const Field &in, Field &out)
|
||||
{
|
||||
Field tmp(in.Grid());
|
||||
_Mat.M(in,tmp);
|
||||
_PV.Mdag(tmp,out);
|
||||
out = out + shift*in;
|
||||
}
|
||||
void AdjOp (const Field &in, Field &out)
|
||||
{
|
||||
Field tmp(in.Grid());
|
||||
_PV.M(in,tmp);
|
||||
_Mat.Mdag(tmp,out);
|
||||
out = out + shift*in;
|
||||
}
|
||||
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2)
|
||||
{
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void HermOp(const Field &in, Field &out){
|
||||
Field tmp(in.Grid());
|
||||
Op(in,tmp);
|
||||
AdjOp(tmp,out);
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Op + shift for any operator (coarse levels).
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
template<class Field>
|
||||
class ShiftedLinearOperator : public LinearOperatorBase<Field> {
|
||||
LinearOperatorBase<Field> &_Op;
|
||||
RealD shift;
|
||||
public:
|
||||
ShiftedLinearOperator(RealD _shift, LinearOperatorBase<Field> &Op) : _Op(Op), shift(_shift) {}
|
||||
void OpDiag (const Field &in, Field &out)
|
||||
{
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void OpDir (const Field &in, Field &out,int dir,int disp)
|
||||
{
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void OpDirAll (const Field &in, std::vector<Field> &out)
|
||||
{
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void Op (const Field &in, Field &out)
|
||||
{
|
||||
_Op.Op(in,out);
|
||||
out = out + shift*in;
|
||||
}
|
||||
void AdjOp (const Field &in, Field &out) {
|
||||
_Op.AdjOp(in,out);
|
||||
out = out + shift*in;
|
||||
}
|
||||
void HermOpAndNorm(const Field &in, Field &out,RealD &n1,RealD &n2)
|
||||
{
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
void HermOp (const Field &in, Field &out)
|
||||
{
|
||||
Field tmp(in.Grid());
|
||||
Op(in,tmp);
|
||||
AdjOp(tmp,out);
|
||||
}
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,318 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./Grid/algorithms/multigrid/Smoothers.h
|
||||
|
||||
Copyright (C) 2026
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution
|
||||
directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
#include <Grid/algorithms/iterative/GCRCoefficients.h>
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Multigrid smoothers as LinearFunction<Field> objects.
|
||||
//
|
||||
// Gathered here from ~15 copies in tests/solver and tests/debug (HDCR /
|
||||
// HDCG era), plus the fixed-polynomial smoothers of 2026:
|
||||
//
|
||||
// ChebyshevSmoother Chebyshev approx to 1/x on [lo,hi], applied
|
||||
// through HermOp (the original HDCG smoother on
|
||||
// a Hermitian shifted operator).
|
||||
// ChebyshevNonHermitianSmoother same polynomial applied through Op(), for
|
||||
// a non-Hermitian (near-normal, real-spectrum)
|
||||
// smoother operator such as shifted PVdagM.
|
||||
// ChebyshevInverter one Chebyshev-corrected step with residual print.
|
||||
// MirsSmoother shifted-MdagM CG, HDCG arXiv:1402.2585.
|
||||
// CGSmoother fixed-iteration CG on an already-shifted
|
||||
// Hermitian operator: the mrhs HDCG smoother.
|
||||
// GCRReplaySmoother replays a GCR's recorded step lengths a_k and
|
||||
// orthogonalisation coefficients b_kj with NO
|
||||
// inner products: one matvec per step, zero
|
||||
// reductions. The "PreconditionerMirsPoly"
|
||||
// idea of 1402.2585 p.13 applied to GCR.
|
||||
//
|
||||
// Recording: PrecGeneralisedConjugateResidualNonHermitian::SetCoefficientRecorder
|
||||
// accumulates per-step means over calls into a GCRCoefficients; construct a
|
||||
// GCRReplaySmoother from it.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
inline RealD InverseApproximation(RealD x){ return 1.0/x; }
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// HermOp-based Chebyshev smoother. The second template parameter and the
|
||||
// 5-argument constructor exist only so the historical call sites
|
||||
// ChebyshevSmoother<LatticeFermion,DomainWallFermionD> S(lo,hi,ord,HermOp,Ddwf);
|
||||
// compile unchanged; the Matrix argument was never used.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
template<class Field,class Matrix=void> class ChebyshevSmoother : public LinearFunction<Field>
|
||||
{
|
||||
public:
|
||||
using LinearFunction<Field>::operator();
|
||||
typedef LinearOperatorBase<Field> FineOperator;
|
||||
FineOperator & _SmootherOperator;
|
||||
Chebyshev<Field> Cheby;
|
||||
ChebyshevSmoother(RealD _lo,RealD _hi,int _ord, FineOperator &SmootherOperator) :
|
||||
_SmootherOperator(SmootherOperator),
|
||||
Cheby(_lo,_hi,_ord,InverseApproximation)
|
||||
{
|
||||
std::cout << GridLogMessage<<" Chebyshev smoother order "<<_ord<<" ["<<_lo<<","<<_hi<<"]"<<std::endl;
|
||||
};
|
||||
template<class M>
|
||||
ChebyshevSmoother(RealD _lo,RealD _hi,int _ord, FineOperator &SmootherOperator, M &) :
|
||||
ChebyshevSmoother(_lo,_hi,_ord,SmootherOperator) {};
|
||||
void operator() (const Field &in, Field &out)
|
||||
{
|
||||
Cheby(_SmootherOperator,in,out);
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Op()-based Chebyshev smoother: x = S(A) r with S the Chebyshev fit to 1/x
|
||||
// on [lo,hi]. Same three-term recurrence as Chebyshev<Field>::operator()
|
||||
// but through Op, for the non-Hermitian smoother operators of the PVdagM
|
||||
// multigrid (real coefficients / near-normal, as the recorded GCR
|
||||
// coefficients show). `order` matvecs, no reductions.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
template<class Field> class ChebyshevNonHermitianSmoother : public LinearFunction<Field>
|
||||
{
|
||||
public:
|
||||
using LinearFunction<Field>::operator();
|
||||
LinearOperatorBase<Field> &Linop;
|
||||
RealD lo, hi; int order;
|
||||
std::vector<RealD> Coeffs;
|
||||
int Verbose = 0;
|
||||
std::string name = "cheb";
|
||||
ChebyshevNonHermitianSmoother(RealD _lo,RealD _hi,int _order,LinearOperatorBase<Field> &Op)
|
||||
: Linop(Op), lo(_lo), hi(_hi), order(_order)
|
||||
{
|
||||
GRID_ASSERT(order>=2);
|
||||
Coeffs.resize(order);
|
||||
for(int j=0;j<order;j++){
|
||||
RealD s=0;
|
||||
for(int k=0;k<order;k++){
|
||||
RealD y=std::cos(M_PI*(k+0.5)/order);
|
||||
RealD x=0.5*(y*(hi-lo)+(hi+lo));
|
||||
s=s+InverseApproximation(x)*std::cos( j*M_PI*(k+0.5)/order );
|
||||
}
|
||||
Coeffs[j] = s * 2.0/order;
|
||||
}
|
||||
std::cout << GridLogMessage<<" ChebyshevNonHermitian smoother order "<<order<<" ["<<lo<<","<<hi<<"]"<<std::endl;
|
||||
}
|
||||
void operator() (const Field &in, Field &out)
|
||||
{
|
||||
GRID_TRACE("ChebyshevSmoother");
|
||||
GridBase *grid=in.Grid();
|
||||
Field T0(grid); T0 = in;
|
||||
Field T1(grid), T2(grid), y(grid);
|
||||
Field *Tnm=&T0, *Tn=&T1, *Tnp=&T2;
|
||||
RealD xscale = 2.0/(hi-lo);
|
||||
RealD mscale = -(hi+lo)/(hi-lo);
|
||||
Linop.Op(T0,y);
|
||||
axpby(T1,xscale,mscale,y,in);
|
||||
axpby(out,0.5*Coeffs[0],Coeffs[1],T0,T1);
|
||||
for(int n=2;n<order;n++){
|
||||
Linop.Op(*Tn,y);
|
||||
axpby(y,xscale,mscale,y,(*Tn));
|
||||
axpby(*Tnp,2.0,-1.0,y,(*Tnm));
|
||||
if ( Coeffs[n] != 0.0 ) axpy(out,Coeffs[n],*Tnp,out);
|
||||
Field *swizzle=Tnm; Tnm=Tn; Tn=Tnp; Tnp=swizzle;
|
||||
}
|
||||
if ( Verbose ) { Linop.Op(out,y); y = y - in; std::cout << GridLogMessage << " " << name << " cheb |r|/|r0| = " << std::sqrt(norm2(y)/norm2(in)) << std::endl; }
|
||||
}
|
||||
};
|
||||
|
||||
template<class Field> class ChebyshevInverter : public LinearFunction<Field>
|
||||
{
|
||||
public:
|
||||
using LinearFunction<Field>::operator();
|
||||
typedef LinearOperatorBase<Field> FineOperator;
|
||||
FineOperator & _Operator;
|
||||
Chebyshev<Field> Cheby;
|
||||
ChebyshevInverter(RealD _lo,RealD _hi,int _ord, FineOperator &Operator) :
|
||||
_Operator(Operator),
|
||||
Cheby(_lo,_hi,_ord,InverseApproximation)
|
||||
{
|
||||
std::cout << GridLogMessage<<" Chebyshev Inverter order "<<_ord<<" ["<<_lo<<","<<_hi<<"]"<<std::endl;
|
||||
};
|
||||
void operator() (const Field &in, Field &out)
|
||||
{
|
||||
Field r(in.Grid());
|
||||
Field AinvR(in.Grid());
|
||||
_Operator.HermOp(out,r);
|
||||
r = in - r; // b - A x
|
||||
Cheby(_Operator,r,AinvR); // A^{-1} ( b - A x ) ~ A^{-1} b - x
|
||||
out = out + AinvR;
|
||||
_Operator.HermOp(out,r);
|
||||
r = in - r; // b - A x
|
||||
RealD rr = norm2(r);
|
||||
RealD ss = norm2(in);
|
||||
std::cout << GridLogMessage << "ChebshevInverse resid " <<::sqrt(rr/ss)<<std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// MIRS: CG on the infra-red shifted MdagM (HDCG, arXiv:1402.2585).
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
template<class Field,class Matrix> class MirsSmoother : public LinearFunction<Field>
|
||||
{
|
||||
public:
|
||||
using LinearFunction<Field>::operator();
|
||||
typedef LinearOperatorBase<Field> FineOperator;
|
||||
Matrix & SmootherMatrix;
|
||||
FineOperator & SmootherOperator;
|
||||
RealD tol;
|
||||
RealD shift;
|
||||
int maxit;
|
||||
MirsSmoother(RealD _shift,RealD _tol,int _maxit,FineOperator &_SmootherOperator,Matrix &_SmootherMatrix) :
|
||||
shift(_shift),tol(_tol),maxit(_maxit),
|
||||
SmootherOperator(_SmootherOperator),
|
||||
SmootherMatrix(_SmootherMatrix)
|
||||
{};
|
||||
void operator() (const Field &in, Field &out)
|
||||
{
|
||||
ZeroGuesser<Field> Guess;
|
||||
ConjugateGradient<Field> CG(tol,maxit,false);
|
||||
Field src(in.Grid());
|
||||
ShiftedMdagMLinearOperator<SparseMatrixBase<Field>,Field> MdagMOp(SmootherMatrix,shift);
|
||||
SmootherOperator.AdjOp(in,src);
|
||||
Guess(src,out);
|
||||
CG(MdagMOp,src,out);
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Fixed-iteration CG on an (already shifted) Hermitian operator, tolerance
|
||||
// zero so the work is fixed. NOT a stationary preconditioner: the CG
|
||||
// coefficients depend on the input, so the map is nonlinear. Use under a
|
||||
// flexible outer (fPcg); BlockCGrQ needs the Chebyshev smoother.
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
template<class Field> class CGSmoother : public LinearFunction<Field>
|
||||
{
|
||||
public:
|
||||
using LinearFunction<Field>::operator();
|
||||
LinearOperatorBase<Field> &_Op;
|
||||
int iters;
|
||||
CGSmoother(int _iters, LinearOperatorBase<Field> &Op) : _Op(Op), iters(_iters)
|
||||
{
|
||||
std::cout << GridLogMessage << "CGSmoother order " << iters << std::endl;
|
||||
}
|
||||
void operator() (const Field &in, Field &out)
|
||||
{
|
||||
ConjugateGradient<Field> CG(0.0,iters,false); // never converges: by design
|
||||
out = Zero();
|
||||
CG(_Op,in,out);
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Replay of a recorded GCR with a trivial preconditioner:
|
||||
// p_0 = r_0 ; x_{k+1} = x_k + a_k p_k ; r_{k+1} = r_k - a_k A p_k ;
|
||||
// p_{k+1} = r_{k+1} + sum_{j<northog(k)} b_kj p_{k-j}
|
||||
// One matvec per step, no reductions, history of mmax p vectors held
|
||||
// persistently (allocated on first use, per grid).
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
template<class Field> class GCRReplaySmoother : public LinearFunction<Field>
|
||||
{
|
||||
public:
|
||||
using LinearFunction<Field>::operator();
|
||||
LinearOperatorBase<Field> &Linop;
|
||||
int mmax, nstep;
|
||||
std::vector<ComplexD> a;
|
||||
std::vector<std::vector<ComplexD> > b;
|
||||
GridBase *hist_grid = nullptr;
|
||||
std::vector<Field> p;
|
||||
int Verbose = 0; // 1: print |r_m|/|r_0| per call (one extra reduction)
|
||||
std::string name = "replay";
|
||||
GCRReplaySmoother(LinearOperatorBase<Field> &Op, const GCRCoefficients &c) : Linop(Op)
|
||||
{
|
||||
mmax = c.mmax; GRID_ASSERT(mmax>=1);
|
||||
nstep = c.Steps(); GRID_ASSERT(nstep>=1);
|
||||
a.resize(nstep); b.resize(nstep);
|
||||
// No std::abs / std::isfinite on ComplexD: it is thrust::complex on HIP
|
||||
// builds and neither overload exists. Work on the real and imaginary
|
||||
// parts explicitly.
|
||||
auto finite = [](ComplexD z){ RealD x=real(z), y=imag(z); return (x==x) && (y==y) && (x-x==0.0) && (y-y==0.0); };
|
||||
auto cabs = [](ComplexD z){ RealD x=real(z), y=imag(z); return std::sqrt(x*x+y*y); };
|
||||
RealD amax=0.0, bmax=0.0;
|
||||
for(int k=0;k<nstep;k++){
|
||||
a[k] = c.A(k);
|
||||
GRID_ASSERT( finite(a[k]) );
|
||||
amax = std::max(amax, cabs(a[k]));
|
||||
b[k].resize(c.NB(k));
|
||||
GRID_ASSERT( c.NB(k) <= mmax-1 );
|
||||
for(int j=0;j<c.NB(k);j++){
|
||||
b[k][j] = c.B(k,j);
|
||||
GRID_ASSERT( finite(b[k][j]) );
|
||||
bmax = std::max(bmax, cabs(b[k][j]));
|
||||
}
|
||||
}
|
||||
std::cout << GridLogMessage << " GCRReplaySmoother: max|a| " << amax << " max|b| " << bmax << std::endl;
|
||||
std::cout << GridLogMessage << " GCRReplaySmoother: " << nstep << " steps, mmax " << mmax
|
||||
<< ", from " << c.Calls() << " recorded calls" << std::endl;
|
||||
}
|
||||
void operator() (const Field &src, Field &psi)
|
||||
{
|
||||
GRID_TRACE("GCRReplaySmoother");
|
||||
GridBase *grid = src.Grid();
|
||||
if ( hist_grid != grid ) {
|
||||
p.clear(); p.reserve(mmax);
|
||||
for(int i=0;i<mmax;i++) p.emplace_back(grid);
|
||||
hist_grid = grid;
|
||||
}
|
||||
Field r(grid), q(grid);
|
||||
r = src;
|
||||
psi = Zero();
|
||||
p[0] = r;
|
||||
RealD r0 = Verbose ? norm2(src) : 0.0;
|
||||
for(int k=0;k<nstep;k++){
|
||||
int kp=k+1, peri_k=k%mmax, peri_kp=kp%mmax;
|
||||
Linop.Op(p[peri_k],q); // q_k = A p_k
|
||||
axpy(psi, a[k], p[peri_k], psi);
|
||||
if ( k==nstep-1 ) {
|
||||
if ( Verbose ) { axpy(r,-a[k],q,r); std::cout << GridLogMessage << " " << name << " replay |r|/|r0| = " << std::sqrt(norm2(r)/r0) << std::endl; }
|
||||
break;
|
||||
}
|
||||
axpy(r, -a[k], q, r);
|
||||
p[peri_kp] = r;
|
||||
for(int j=0;j<(int)b[k].size();j++){
|
||||
int peri_back=(k-j)%mmax;
|
||||
axpy(p[peri_kp], b[k][j], p[peri_back], p[peri_kp]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// A LinearFunction that forwards to a replaceable target: lets a V-cycle be
|
||||
// built once around a smoother slot whose implementation is swapped at run
|
||||
// time (record with the adaptive GCR, then replay the polynomial).
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
template<class Field> class SwitchableSmoother : public LinearFunction<Field>
|
||||
{
|
||||
public:
|
||||
using LinearFunction<Field>::operator();
|
||||
LinearFunction<Field> *current;
|
||||
std::string label;
|
||||
SwitchableSmoother(LinearFunction<Field> &initial, const std::string &l="initial") : current(&initial), label(l) {}
|
||||
void Set(LinearFunction<Field> &f, const std::string &l)
|
||||
{
|
||||
current = &f; label = l;
|
||||
std::cout << GridLogMessage << " SwitchableSmoother -> " << l << std::endl;
|
||||
}
|
||||
void operator() (const Field &in, Field &out) { (*current)(in,out); }
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,704 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./lib/algorithms/GeneralCoarsenedMatrix.h
|
||||
|
||||
Copyright (C) 2015
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
#include <Grid/qcd/QCD.h> // needed for Dagger(Yes|No), Inverse(Yes|No)
|
||||
|
||||
#include <Grid/lattice/PaddedCell.h>
|
||||
#include <Grid/stencil/GeneralLocalStencil.h>
|
||||
#include <Grid/algorithms/deflation/MultiRHSBlockProject.h>
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
// Fine Object == (per site) type of fine field
|
||||
// nbasis == number of deflation vectors
|
||||
template<class Fobj,class CComplex,int nbasis>
|
||||
class DeprecatedGeneralCoarsenedMatrix : public SparseMatrixBase<Lattice<iVector<CComplex,nbasis > > > {
|
||||
public:
|
||||
|
||||
typedef DeprecatedGeneralCoarsenedMatrix<Fobj,CComplex,nbasis> GeneralCoarseOp;
|
||||
typedef iVector<CComplex,nbasis > siteVector;
|
||||
typedef iMatrix<CComplex,nbasis > siteMatrix;
|
||||
typedef Lattice<iScalar<CComplex> > CoarseComplexField;
|
||||
typedef Lattice<siteVector> CoarseVector;
|
||||
typedef Lattice<iMatrix<CComplex,nbasis > > CoarseMatrix;
|
||||
typedef iMatrix<CComplex,nbasis > Cobj;
|
||||
typedef iVector<CComplex,nbasis > Cvec;
|
||||
typedef Lattice< CComplex > CoarseScalar; // used for inner products on fine field
|
||||
typedef Lattice<Fobj > FineField;
|
||||
typedef Lattice<CComplex > FineComplexField;
|
||||
typedef CoarseVector Field;
|
||||
////////////////////
|
||||
// Data members
|
||||
////////////////////
|
||||
int hermitian;
|
||||
GridBase * _FineGrid;
|
||||
GridCartesian * _CoarseGrid;
|
||||
NonLocalStencilGeometry &geom;
|
||||
PaddedCell Cell;
|
||||
GeneralLocalStencil Stencil;
|
||||
|
||||
std::vector<CoarseMatrix> _A;
|
||||
std::vector<CoarseMatrix> _Adag;
|
||||
std::vector<CoarseVector> MultTemporaries;
|
||||
|
||||
int64_t MultCalls;
|
||||
double MultFlopsAccum;
|
||||
double MultUsecAccum;
|
||||
|
||||
///////////////////////
|
||||
// Interface
|
||||
///////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Bilingual accessors: everything a consumer needs to read the operator
|
||||
// without knowing which of the three coarse classes it holds. The D
|
||||
// dimensional grid the elements live on, the geometry they are indexed by,
|
||||
// and one unpadded point at a time (a whole npoint vector is too much
|
||||
// memory at production nbasis).
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
GridCartesian * CoarseGridD(void) { return _CoarseGrid; };
|
||||
NonLocalStencilGeometry & Geometry(void) { return geom; };
|
||||
void ExtractMatrix(int p,CoarseMatrix &A) { A = Cell.Extract(_A[p]); };
|
||||
|
||||
GridBase * Grid(void) { return _CoarseGrid; }; // this is all the linalg routines need to know
|
||||
GridBase * FineGrid(void) { return _FineGrid; }; // this is all the linalg routines need to know
|
||||
GridCartesian * CoarseGrid(void) { return _CoarseGrid; }; // this is all the linalg routines need to know
|
||||
|
||||
/* void ShiftMatrix(RealD shift)
|
||||
{
|
||||
int Nd=_FineGrid->Nd();
|
||||
Coordinate zero_shift(Nd,0);
|
||||
for(int p=0;p<geom.npoint;p++){
|
||||
if ( zero_shift==geom.shifts[p] ) {
|
||||
_A[p] = _A[p]+shift;
|
||||
// _Adag[p] = _Adag[p]+shift;
|
||||
}
|
||||
}
|
||||
}
|
||||
void ProjectNearestNeighbour(RealD shift, GeneralCoarseOp &CopyMe)
|
||||
{
|
||||
int nfound=0;
|
||||
std::cout << GridLogMessage <<"DeprecatedGeneralCoarsenedMatrix::ProjectNearestNeighbour "<< CopyMe._A[0].Grid()<<std::endl;
|
||||
for(int p=0;p<geom.npoint;p++){
|
||||
for(int pp=0;pp<CopyMe.geom.npoint;pp++){
|
||||
// Search for the same relative shift
|
||||
// Avoids brutal handling of Grid pointers
|
||||
if ( CopyMe.geom.shifts[pp]==geom.shifts[p] ) {
|
||||
_A[p] = CopyMe.Cell.Extract(CopyMe._A[pp]);
|
||||
// _Adag[p] = CopyMe.Cell.Extract(CopyMe._Adag[pp]);
|
||||
nfound++;
|
||||
}
|
||||
}
|
||||
}
|
||||
GRID_ASSERT(nfound==geom.npoint);
|
||||
ExchangeCoarseLinks();
|
||||
}
|
||||
*/
|
||||
|
||||
DeprecatedGeneralCoarsenedMatrix(NonLocalStencilGeometry &_geom,GridBase *FineGrid, GridCartesian * CoarseGrid,int _herm=1)
|
||||
: geom(_geom),
|
||||
_FineGrid(FineGrid),
|
||||
_CoarseGrid(CoarseGrid),
|
||||
hermitian(_herm),
|
||||
Cell(_geom.Depth(),_CoarseGrid),
|
||||
Stencil(Cell.grids.back(),geom.shifts),
|
||||
MultCalls(0), MultFlopsAccum(0.0), MultUsecAccum(0.0)
|
||||
{
|
||||
{
|
||||
int npoint = _geom.npoint;
|
||||
}
|
||||
_A.resize(geom.npoint,CoarseGrid);
|
||||
if ( !hermitian ) _Adag.resize(geom.npoint,CoarseGrid);
|
||||
}
|
||||
void M (const CoarseVector &in, CoarseVector &out)
|
||||
{
|
||||
Mult(_A,in,out);
|
||||
}
|
||||
void Mdag (const CoarseVector &in, CoarseVector &out)
|
||||
{
|
||||
if(hermitian)
|
||||
Mult(_A,in,out);
|
||||
else
|
||||
Mult(_Adag,in,out);
|
||||
}
|
||||
void Mult (std::vector<CoarseMatrix> &A,const CoarseVector &in, CoarseVector &out)
|
||||
{
|
||||
RealD tviews=0; RealD ttot=0; RealD tmult=0; RealD texch=0; RealD text=0; RealD ttemps=0; RealD tcopy=0;
|
||||
RealD tmult2=0;
|
||||
|
||||
ttot=-usecond();
|
||||
conformable(CoarseGrid(),in.Grid());
|
||||
conformable(in.Grid(),out.Grid());
|
||||
out.Checkerboard() = in.Checkerboard();
|
||||
CoarseVector tin=in;
|
||||
|
||||
texch-=usecond();
|
||||
CoarseVector pin = Cell.ExchangePeriodic(tin);
|
||||
texch+=usecond();
|
||||
|
||||
CoarseVector pout(pin.Grid());
|
||||
|
||||
int npoint = geom.npoint;
|
||||
typedef LatticeView<Cobj> Aview;
|
||||
typedef LatticeView<Cvec> Vview;
|
||||
|
||||
const int Nsimd = CComplex::Nsimd();
|
||||
|
||||
int64_t osites=pin.Grid()->oSites();
|
||||
|
||||
RealD flops = 1.0* npoint * nbasis * nbasis * 8.0 * osites * CComplex::Nsimd();
|
||||
RealD bytes = 1.0*osites*sizeof(siteMatrix)*npoint
|
||||
+ 2.0*osites*sizeof(siteVector)*npoint;
|
||||
|
||||
{
|
||||
tviews-=usecond();
|
||||
autoView( in_v , pin, AcceleratorRead);
|
||||
autoView( out_v , pout, AcceleratorWriteDiscard);
|
||||
autoView( Stencil_v , Stencil, AcceleratorRead);
|
||||
tviews+=usecond();
|
||||
|
||||
// Static and prereserve to keep UVM region live and not resized across multiple calls
|
||||
ttemps-=usecond();
|
||||
MultTemporaries.resize(npoint,pin.Grid());
|
||||
ttemps+=usecond();
|
||||
std::vector<Aview> AcceleratorViewContainer_h;
|
||||
std::vector<Vview> AcceleratorVecViewContainer_h;
|
||||
|
||||
tviews-=usecond();
|
||||
for(int p=0;p<npoint;p++) {
|
||||
AcceleratorViewContainer_h.push_back( A[p].View(AcceleratorRead));
|
||||
AcceleratorVecViewContainer_h.push_back(MultTemporaries[p].View(AcceleratorWrite));
|
||||
}
|
||||
tviews+=usecond();
|
||||
|
||||
static deviceVector<Aview> AcceleratorViewContainer; AcceleratorViewContainer.resize(npoint);
|
||||
static deviceVector<Vview> AcceleratorVecViewContainer; AcceleratorVecViewContainer.resize(npoint);
|
||||
|
||||
auto Aview_p = &AcceleratorViewContainer[0];
|
||||
auto Vview_p = &AcceleratorVecViewContainer[0];
|
||||
tcopy-=usecond();
|
||||
acceleratorCopyToDevice(&AcceleratorViewContainer_h[0],&AcceleratorViewContainer[0],npoint *sizeof(Aview));
|
||||
acceleratorCopyToDevice(&AcceleratorVecViewContainer_h[0],&AcceleratorVecViewContainer[0],npoint *sizeof(Vview));
|
||||
tcopy+=usecond();
|
||||
|
||||
tmult-=usecond();
|
||||
accelerator_for(spb, osites*nbasis*npoint, Nsimd, {
|
||||
typedef decltype(coalescedRead(in_v[0](0))) calcComplex;
|
||||
int32_t ss = spb/(nbasis*npoint);
|
||||
int32_t bp = spb%(nbasis*npoint);
|
||||
int32_t point= bp/nbasis;
|
||||
int32_t b = bp%nbasis;
|
||||
auto SE = Stencil_v.GetEntry(point,ss);
|
||||
auto nbr = coalescedReadGeneralPermute(in_v[SE->_offset],SE->_permute,Nd);
|
||||
auto res = coalescedRead(Aview_p[point][ss](0,b))*nbr(0);
|
||||
for(int bb=1;bb<nbasis;bb++) {
|
||||
res = res + coalescedRead(Aview_p[point][ss](bb,b))*nbr(bb);
|
||||
}
|
||||
coalescedWrite(Vview_p[point][ss](b),res);
|
||||
});
|
||||
tmult2-=usecond();
|
||||
accelerator_for(sb, osites*nbasis, Nsimd, {
|
||||
int ss = sb/nbasis;
|
||||
int b = sb%nbasis;
|
||||
auto res = coalescedRead(Vview_p[0][ss](b));
|
||||
for(int point=1;point<npoint;point++){
|
||||
res = res + coalescedRead(Vview_p[point][ss](b));
|
||||
}
|
||||
coalescedWrite(out_v[ss](b),res);
|
||||
});
|
||||
tmult2+=usecond();
|
||||
tmult+=usecond();
|
||||
for(int p=0;p<npoint;p++) {
|
||||
AcceleratorViewContainer_h[p].ViewClose();
|
||||
AcceleratorVecViewContainer_h[p].ViewClose();
|
||||
}
|
||||
}
|
||||
|
||||
text-=usecond();
|
||||
out = Cell.Extract(pout);
|
||||
text+=usecond();
|
||||
ttot+=usecond();
|
||||
|
||||
MultCalls++;
|
||||
MultFlopsAccum += flops;
|
||||
MultUsecAccum += ttot;
|
||||
std::cout << GridLogPerformance
|
||||
<< "Coarse Mult call " << MultCalls
|
||||
<< " tot " << ttot << " us"
|
||||
<< " kernel " << tmult << " us"
|
||||
<< " kernel " << flops/tmult*1e-3 << " GFlop/s"
|
||||
<< " overall " << MultFlopsAccum/MultUsecAccum*1e-3 << " GFlop/s (cumul)"
|
||||
<< " bw " << bytes/tmult*1e-3 << " GB/s"
|
||||
<< std::endl;
|
||||
|
||||
};
|
||||
|
||||
void PopulateAdag(void)
|
||||
{
|
||||
#if 0
|
||||
// Serial global peek/poke reference implementation
|
||||
for(int64_t bidx=0;bidx<CoarseGrid()->gSites() ;bidx++){
|
||||
Coordinate bcoor;
|
||||
CoarseGrid()->GlobalIndexToGlobalCoor(bidx,bcoor);
|
||||
|
||||
for(int p=0;p<geom.npoint;p++){
|
||||
Coordinate scoor = bcoor;
|
||||
for(int mu=0;mu<bcoor.size();mu++){
|
||||
int L = CoarseGrid()->GlobalDimensions()[mu];
|
||||
scoor[mu] = (bcoor[mu] - geom.shifts[p][mu] + L) % L; // Modulo arithmetic
|
||||
}
|
||||
// Flip to poke/peekLocalSite and not too bad
|
||||
auto link = peekSite(_A[p],scoor);
|
||||
int pp = geom.Reverse(p);
|
||||
pokeSite(adj(link),_Adag[pp],bcoor);
|
||||
}
|
||||
}
|
||||
#else
|
||||
// Parallel: _Adag[pp](x) = adj( _A[p](x + s_pp) ), pp = Reverse(p), s_pp = -s_p.
|
||||
// The neighbour fetch reuses the same padded-cell + stencil machinery as Mult,
|
||||
// reading one matrix element per coalesced access so no whole site matrix
|
||||
// (230KB at nbasis=60) ever lands on a GPU thread stack (HIP limit 128KB).
|
||||
// Halo sites compute garbage neighbours; Cell.Extract discards them.
|
||||
// Must run on the unpadded _A, i.e. before ExchangeCoarseLinks.
|
||||
const int Nsimd = CComplex::Nsimd();
|
||||
for(int p=0;p<geom.npoint;p++){
|
||||
int pp = geom.Reverse(p);
|
||||
CoarseMatrix Apad = Cell.ExchangePeriodic(_A[p]);
|
||||
CoarseMatrix Dpad(Apad.Grid());
|
||||
int64_t osites = Apad.Grid()->oSites();
|
||||
{
|
||||
autoView( Apad_v , Apad, AcceleratorRead);
|
||||
autoView( Dpad_v , Dpad, AcceleratorWriteDiscard);
|
||||
autoView( Stencil_v, Stencil, AcceleratorRead);
|
||||
accelerator_for(sj, osites*nbasis, Nsimd, {
|
||||
int32_t ss = sj/nbasis;
|
||||
int32_t j = sj%nbasis;
|
||||
auto SE = Stencil_v.GetEntry(pp,ss);
|
||||
for(int i=0;i<nbasis;i++){
|
||||
auto z = coalescedReadGeneralPermute(Apad_v[SE->_offset](i,j),SE->_permute,Nd);
|
||||
coalescedWrite(Dpad_v[ss](j,i),conjugate(z));
|
||||
}
|
||||
});
|
||||
}
|
||||
_Adag[pp] = Cell.Extract(Dpad);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// A) Only reduced flops option is to use a padded cell of depth 4
|
||||
// and apply MpcDagMpc in the padded cell.
|
||||
//
|
||||
// Makes for ONE application of MpcDagMpc per vector instead of 30 or 80.
|
||||
// With the effective cell size around (B+8)^4 perhaps 12^4/4^4 ratio
|
||||
// Cost is 81x more, same as stencil size.
|
||||
//
|
||||
// But: can eliminate comms and do as local dirichlet.
|
||||
//
|
||||
// Local exchange gauge field once.
|
||||
// Apply to all vectors, local only computation.
|
||||
// Must exchange ghost subcells in reverse process of PaddedCell to take inner products
|
||||
//
|
||||
// B) Can reduce cost: pad by 1, apply Deo (4^4+6^4+8^4+8^4 )/ (4x 4^4)
|
||||
// pad by 2, apply Doe
|
||||
// pad by 3, apply Deo
|
||||
// then break out 8x directions; cost is ~10x MpcDagMpc per vector
|
||||
//
|
||||
// => almost factor of 10 in setup cost, excluding data rearrangement
|
||||
//
|
||||
// Intermediates -- ignore the corner terms, leave approximate and force Hermitian
|
||||
// Intermediates -- pad by 2 and apply 1+8+24 = 33 times.
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
// BFM HDCG style approach: Solve a system of equations to get Aij
|
||||
//////////////////////////////////////////////////////////
|
||||
/*
|
||||
* Here, k,l index which possible shift within the 3^Nd "ball" connected by MdagM.
|
||||
*
|
||||
* conj(phases[block]) proj[k][ block*Nvec+j ] = \sum_ball e^{i q_k . delta} < phi_{block,j} | MdagM | phi_{(block+delta),i} >
|
||||
* = \sum_ball e^{iqk.delta} A_ji
|
||||
*
|
||||
* Must invert matrix M_k,l = e^[i q_k . delta_l]
|
||||
*
|
||||
* Where q_k = delta_k . (2*M_PI/global_nb[mu])
|
||||
*/
|
||||
#if 0
|
||||
void CoarsenOperator(LinearOperatorBase<Lattice<Fobj> > &linop,
|
||||
Aggregation<Fobj,CComplex,nbasis> & Subspace)
|
||||
{
|
||||
std::cout << GridLogMessage<< "GeneralCoarsenMatrix "<< std::endl;
|
||||
GridBase *grid = FineGrid();
|
||||
|
||||
RealD tproj=0.0;
|
||||
RealD teigen=0.0;
|
||||
RealD tmat=0.0;
|
||||
RealD tphase=0.0;
|
||||
RealD tinv=0.0;
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Orthogonalise the subblocks over the basis
|
||||
/////////////////////////////////////////////////////////////
|
||||
CoarseScalar InnerProd(CoarseGrid());
|
||||
blockOrthogonalise(InnerProd,Subspace.subspace);
|
||||
|
||||
const int npoint = geom.npoint;
|
||||
|
||||
Coordinate clatt = CoarseGrid()->GlobalDimensions();
|
||||
int Nd = CoarseGrid()->Nd();
|
||||
|
||||
/*
|
||||
* Here, k,l index which possible momentum/shift within the N-points connected by MdagM.
|
||||
* Matrix index i is mapped to this shift via
|
||||
* geom.shifts[i]
|
||||
*
|
||||
* conj(pha[block]) proj[k (which mom)][j (basis vec cpt)][block]
|
||||
* = \sum_{l in ball} e^{i q_k . delta_l} < phi_{block,j} | MdagM | phi_{(block+delta_l),i} >
|
||||
* = \sum_{l in ball} e^{iqk.delta_l} A_ji^{b.b+l}
|
||||
* = M_{kl} A_ji^{b.b+l}
|
||||
*
|
||||
* Must assemble and invert matrix M_k,l = e^[i q_k . delta_l]
|
||||
*
|
||||
* Where q_k = delta_k . (2*M_PI/global_nb[mu])
|
||||
*
|
||||
* Then A{ji}^{b,b+l} = M^{-1}_{lm} ComputeProj_{m,b,i,j}
|
||||
*/
|
||||
teigen-=usecond();
|
||||
Eigen::MatrixXcd Mkl = Eigen::MatrixXcd::Zero(npoint,npoint);
|
||||
Eigen::MatrixXcd invMkl = Eigen::MatrixXcd::Zero(npoint,npoint);
|
||||
ComplexD ci(0.0,1.0);
|
||||
for(int k=0;k<npoint;k++){ // Loop over momenta
|
||||
|
||||
for(int l=0;l<npoint;l++){ // Loop over nbr relative
|
||||
ComplexD phase(0.0,0.0);
|
||||
for(int mu=0;mu<Nd;mu++){
|
||||
RealD TwoPiL = M_PI * 2.0/ clatt[mu];
|
||||
phase=phase+TwoPiL*geom.shifts[k][mu]*geom.shifts[l][mu];
|
||||
}
|
||||
phase=exp(phase*ci);
|
||||
Mkl(k,l) = phase;
|
||||
}
|
||||
}
|
||||
invMkl = Mkl.inverse();
|
||||
teigen+=usecond();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Now compute the matrix elements of linop between the orthonormal
|
||||
// set of vectors.
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
FineField phaV(grid); // Phased block basis vector
|
||||
FineField MphaV(grid);// Matrix applied
|
||||
CoarseVector coarseInner(CoarseGrid());
|
||||
|
||||
std::vector<CoarseVector> ComputeProj(npoint,CoarseGrid());
|
||||
std::vector<CoarseVector> FT(npoint,CoarseGrid());
|
||||
for(int i=0;i<nbasis;i++){// Loop over basis vectors
|
||||
std::cout << GridLogMessage<< "CoarsenMatrixColoured vec "<<i<<"/"<<nbasis<< std::endl;
|
||||
for(int p=0;p<npoint;p++){ // Loop over momenta in npoint
|
||||
/////////////////////////////////////////////////////
|
||||
// Stick a phase on every block
|
||||
/////////////////////////////////////////////////////
|
||||
tphase-=usecond();
|
||||
CoarseComplexField coor(CoarseGrid());
|
||||
CoarseComplexField pha(CoarseGrid()); pha=Zero();
|
||||
for(int mu=0;mu<Nd;mu++){
|
||||
LatticeCoordinate(coor,mu);
|
||||
RealD TwoPiL = M_PI * 2.0/ clatt[mu];
|
||||
pha = pha + (TwoPiL * geom.shifts[p][mu]) * coor;
|
||||
}
|
||||
pha =exp(pha*ci);
|
||||
phaV=Zero();
|
||||
blockZAXPY(phaV,pha,Subspace.subspace[i],phaV);
|
||||
tphase+=usecond();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Multiple phased subspace vector by matrix and project to subspace
|
||||
// Remove local bulk phase to leave relative phases
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
tmat-=usecond();
|
||||
linop.Op(phaV,MphaV);
|
||||
tmat+=usecond();
|
||||
|
||||
tproj-=usecond();
|
||||
blockProject(coarseInner,MphaV,Subspace.subspace);
|
||||
coarseInner = conjugate(pha) * coarseInner;
|
||||
|
||||
ComputeProj[p] = coarseInner;
|
||||
tproj+=usecond();
|
||||
|
||||
}
|
||||
|
||||
tinv-=usecond();
|
||||
for(int k=0;k<npoint;k++){
|
||||
FT[k] = Zero();
|
||||
for(int l=0;l<npoint;l++){
|
||||
FT[k]= FT[k]+ invMkl(l,k)*ComputeProj[l];
|
||||
}
|
||||
|
||||
int osites=CoarseGrid()->oSites();
|
||||
autoView( A_v , _A[k], AcceleratorWrite);
|
||||
autoView( FT_v , FT[k], AcceleratorRead);
|
||||
accelerator_for(sss, osites, nbasis, {
|
||||
#ifdef GRID_SIMT
|
||||
int j = acceleratorSIMTlane(nbasis);
|
||||
A_v[sss](i,j) = FT_v[sss](j);
|
||||
#else
|
||||
// CPU build: acceleratorSIMTlane()==0 -- an un-looped SIMT tensor
|
||||
// index writes ONLY j=0 and silently drops the other nbasis-1
|
||||
// columns (caught by Test_schur_dense_coarse import certificate,
|
||||
// 2026-08-14). Loop explicitly.
|
||||
for(int j=0;j<nbasis;j++){
|
||||
A_v[sss](i,j) = FT_v[sss](j);
|
||||
}
|
||||
#endif
|
||||
});
|
||||
}
|
||||
tinv+=usecond();
|
||||
}
|
||||
|
||||
// Only needed if nonhermitian
|
||||
if ( ! hermitian ) {
|
||||
std::cout << GridLogMessage<<"PopulateAdag "<<std::endl;
|
||||
PopulateAdag();
|
||||
}
|
||||
|
||||
// Need to write something to populate Adag from A
|
||||
ExchangeCoarseLinks();
|
||||
std::cout << GridLogMessage<<"CoarsenOperator eigen "<<teigen<<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"CoarsenOperator phase "<<tphase<<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"CoarsenOperator mat "<<tmat <<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"CoarsenOperator proj "<<tproj<<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"CoarsenOperator inv "<<tinv<<" us"<<std::endl;
|
||||
}
|
||||
#else
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Galerkin projection of matrix
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
void CoarsenOperator(LinearOperatorBase<Lattice<Fobj> > &linop,
|
||||
Aggregation<Fobj,CComplex,nbasis> & Subspace)
|
||||
{
|
||||
CoarsenOperator(linop,Subspace,Subspace);
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Petrov - Galerkin projection of matrix
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
void CoarsenOperator(LinearOperatorBase<Lattice<Fobj> > &linop,
|
||||
Aggregation<Fobj,CComplex,nbasis> & U,
|
||||
Aggregation<Fobj,CComplex,nbasis> & V)
|
||||
{
|
||||
std::cout << GridLogMessage<< "GeneralCoarsenMatrix "<< std::endl;
|
||||
GridBase *grid = FineGrid();
|
||||
|
||||
RealD tproj=0.0;
|
||||
RealD teigen=0.0;
|
||||
RealD tmat=0.0;
|
||||
RealD tphase=0.0;
|
||||
RealD tphaseBZ=0.0;
|
||||
RealD tinv=0.0;
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Orthogonalise the subblocks over the basis
|
||||
/////////////////////////////////////////////////////////////
|
||||
CoarseScalar InnerProd(CoarseGrid());
|
||||
blockOrthogonalise(InnerProd,V.subspace);
|
||||
blockOrthogonalise(InnerProd,U.subspace);
|
||||
|
||||
const int npoint = geom.npoint;
|
||||
|
||||
Coordinate clatt = CoarseGrid()->GlobalDimensions();
|
||||
int Nd = CoarseGrid()->Nd();
|
||||
|
||||
/*
|
||||
* Here, k,l index which possible momentum/shift within the N-points connected by MdagM.
|
||||
* Matrix index i is mapped to this shift via
|
||||
* geom.shifts[i]
|
||||
*
|
||||
* conj(pha[block]) proj[k (which mom)][j (basis vec cpt)][block]
|
||||
* = \sum_{l in ball} e^{i q_k . delta_l} < phi_{block,j} | MdagM | phi_{(block+delta_l),i} >
|
||||
* = \sum_{l in ball} e^{iqk.delta_l} A_ji^{b.b+l}
|
||||
* = M_{kl} A_ji^{b.b+l}
|
||||
*
|
||||
* Must assemble and invert matrix M_k,l = e^[i q_k . delta_l]
|
||||
*
|
||||
* Where q_k = delta_k . (2*M_PI/global_nb[mu])
|
||||
*
|
||||
* Then A{ji}^{b,b+l} = M^{-1}_{lm} ComputeProj_{m,b,i,j}
|
||||
*/
|
||||
teigen-=usecond();
|
||||
Eigen::MatrixXcd Mkl = Eigen::MatrixXcd::Zero(npoint,npoint);
|
||||
Eigen::MatrixXcd invMkl = Eigen::MatrixXcd::Zero(npoint,npoint);
|
||||
ComplexD ci(0.0,1.0);
|
||||
for(int k=0;k<npoint;k++){ // Loop over momenta
|
||||
|
||||
for(int l=0;l<npoint;l++){ // Loop over nbr relative
|
||||
ComplexD phase(0.0,0.0);
|
||||
for(int mu=0;mu<Nd;mu++){
|
||||
RealD TwoPiL = M_PI * 2.0/ clatt[mu];
|
||||
phase=phase+TwoPiL*geom.shifts[k][mu]*geom.shifts[l][mu];
|
||||
}
|
||||
phase=exp(phase*ci);
|
||||
Mkl(k,l) = phase;
|
||||
}
|
||||
}
|
||||
invMkl = Mkl.inverse();
|
||||
teigen+=usecond();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Now compute the matrix elements of linop between the orthonormal
|
||||
// set of vectors.
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
std::vector<FineComplexField> phaF(npoint,grid);
|
||||
std::vector<CoarseComplexField> pha(npoint,CoarseGrid());
|
||||
|
||||
typedef typename CComplex::scalar_type SComplex;
|
||||
FineComplexField one(grid); one=SComplex(1.0);
|
||||
FineComplexField zz(grid); zz = Zero();
|
||||
tphase=-usecond();
|
||||
for(int p=0;p<npoint;p++){ // Loop over momenta in npoint
|
||||
/////////////////////////////////////////////////////
|
||||
// Stick a phase on every block
|
||||
/////////////////////////////////////////////////////
|
||||
CoarseComplexField coor(CoarseGrid());
|
||||
pha[p]=Zero();
|
||||
for(int mu=0;mu<Nd;mu++){
|
||||
LatticeCoordinate(coor,mu);
|
||||
RealD TwoPiL = M_PI * 2.0/ clatt[mu];
|
||||
pha[p] = pha[p] + (TwoPiL * geom.shifts[p][mu]) * coor;
|
||||
}
|
||||
pha[p] =exp(pha[p]*ci);
|
||||
|
||||
blockZAXPY(phaF[p],pha[p],one,zz);
|
||||
|
||||
}
|
||||
tphase+=usecond();
|
||||
|
||||
// Import basis into BLAS layout once; blockProject then reads it once per
|
||||
// basis vector rather than once per (i,p) as in scalar blockProject.
|
||||
// Process all npoint in a single batch.
|
||||
MultiRHSBlockProject<FineField> Projector;
|
||||
Projector.Allocate(nbasis, grid, CoarseGrid());
|
||||
Projector.ImportBasis(U.subspace);
|
||||
|
||||
std::vector<FineField> phaV_batch(npoint, grid);
|
||||
std::vector<FineField> MphaV_batch(npoint, grid);
|
||||
std::vector<CoarseVector> proj_batch(npoint, CoarseGrid());
|
||||
std::vector<CoarseVector> ComputeProj(npoint, CoarseGrid());
|
||||
std::vector<CoarseVector> FT(npoint, CoarseGrid());
|
||||
|
||||
// Pre-allocate BLAS_F and BLAS_C to avoid repeated hipMalloc/hipFree of
|
||||
// ~5.6 GB per blockProject call, which hangs on ROCm for large allocations.
|
||||
Projector.BLAS_F.resize(Projector.fine_vol * Projector.words * npoint);
|
||||
Projector.BLAS_C.resize(Projector.coarse_vol * nbasis * npoint);
|
||||
|
||||
for(int i=0;i<nbasis;i++){// Loop over basis vectors
|
||||
accelerator_barrier(); // ensure prior iteration's async writes are retired
|
||||
std::cout << GridLogMessage<< "CoarsenMatrixColoured vec "<<i<<"/"<<nbasis<< std::endl;
|
||||
|
||||
tphaseBZ-=usecond();
|
||||
for(int p=0;p<npoint;p++)
|
||||
phaV_batch[p] = phaF[p] * V.subspace[i];
|
||||
tphaseBZ+=usecond();
|
||||
std::cout << GridLogMessage<< "CoarsenMatrixColoured vec "<<i<<" phaseBZ done"<< std::endl;
|
||||
|
||||
tmat-=usecond();
|
||||
for(int p=0;p<npoint;p++)
|
||||
linop.Op(phaV_batch[p], MphaV_batch[p]);
|
||||
tmat+=usecond();
|
||||
std::cout << GridLogMessage<< "CoarsenMatrixColoured vec "<<i<<" mat done"<< std::endl;
|
||||
|
||||
// One batched GEMM reads BLAS_V once for all npoint vectors.
|
||||
tproj-=usecond();
|
||||
Projector.blockProject(MphaV_batch, proj_batch);
|
||||
std::cout << GridLogMessage<< "CoarsenMatrixColoured vec "<<i<<" blockProject done"<< std::endl;
|
||||
for(int p=0;p<npoint;p++)
|
||||
ComputeProj[p] = conjugate(pha[p]) * proj_batch[p];
|
||||
tproj+=usecond();
|
||||
std::cout << GridLogMessage<< "CoarsenMatrixColoured vec "<<i<<" proj done"<< std::endl;
|
||||
|
||||
tinv-=usecond();
|
||||
for(int k=0;k<npoint;k++){
|
||||
FT[k] = Zero();
|
||||
for(int l=0;l<npoint;l++){
|
||||
FT[k]= FT[k]+ invMkl(l,k)*ComputeProj[l];
|
||||
}
|
||||
|
||||
int osites=CoarseGrid()->oSites();
|
||||
autoView( A_v , _A[k], AcceleratorWrite);
|
||||
autoView( FT_v , FT[k], AcceleratorRead);
|
||||
accelerator_for(sss, osites, nbasis, {
|
||||
#ifdef GRID_SIMT
|
||||
int j = acceleratorSIMTlane(nbasis);
|
||||
A_v[sss](i,j) = FT_v[sss](j);
|
||||
#else
|
||||
// CPU build: acceleratorSIMTlane()==0 -- an un-looped SIMT tensor
|
||||
// index writes ONLY j=0 and silently drops the other nbasis-1
|
||||
// columns (caught by Test_schur_dense_coarse import certificate,
|
||||
// 2026-08-14). Loop explicitly.
|
||||
for(int j=0;j<nbasis;j++){
|
||||
A_v[sss](i,j) = FT_v[sss](j);
|
||||
}
|
||||
#endif
|
||||
});
|
||||
}
|
||||
tinv+=usecond();
|
||||
}
|
||||
|
||||
// Only needed if nonhermitian
|
||||
if ( ! hermitian ) {
|
||||
std::cout << GridLogMessage<<"PopulateAdag "<<std::endl;
|
||||
PopulateAdag();
|
||||
}
|
||||
|
||||
// for(int p=0;p<geom.npoint;p++){
|
||||
// std::cout << " _A["<<p<<"] "<<norm2(_A[p])<<std::endl;
|
||||
// }
|
||||
|
||||
// Need to write something to populate Adag from A
|
||||
ExchangeCoarseLinks();
|
||||
std::cout << GridLogMessage<<"CoarsenOperator eigen "<<teigen<<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"CoarsenOperator phase "<<tphase<<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"CoarsenOperator phaseBZ "<<tphaseBZ<<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"CoarsenOperator mat "<<tmat <<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"CoarsenOperator proj "<<tproj<<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"CoarsenOperator inv "<<tinv<<" us"<<std::endl;
|
||||
}
|
||||
#endif
|
||||
void ExchangeCoarseLinks(void){
|
||||
for(int p=0;p<geom.npoint;p++){
|
||||
_A[p] = Cell.ExchangePeriodic(_A[p]);
|
||||
if ( !hermitian ) _Adag[p]= Cell.ExchangePeriodic(_Adag[p]);
|
||||
}
|
||||
}
|
||||
virtual void Mdiag (const Field &in, Field &out){ GRID_ASSERT(0);};
|
||||
virtual void Mdir (const Field &in, Field &out,int dir, int disp){assert(0);};
|
||||
virtual void MdirAll (const Field &in, std::vector<Field> &out){assert(0);};
|
||||
};
|
||||
|
||||
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,758 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./lib/algorithms/GeneralCoarsenedMatrixMultiRHS.h
|
||||
|
||||
Copyright (C) 2015
|
||||
|
||||
Author: Peter Boyle <pboyle@bnl.gov>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
|
||||
// Fine Object == (per site) type of fine field
|
||||
// nbasis == number of deflation vectors
|
||||
template<class Fobj,class CComplex,int nbasis>
|
||||
class DeprecatedMultiGeneralCoarsenedMatrix : public SparseMatrixBase<Lattice<iVector<CComplex,nbasis > > > {
|
||||
public:
|
||||
typedef typename CComplex::scalar_object SComplex;
|
||||
typedef DeprecatedGeneralCoarsenedMatrix<Fobj,CComplex,nbasis> GeneralCoarseOp;
|
||||
typedef DeprecatedMultiGeneralCoarsenedMatrix<Fobj,CComplex,nbasis> MultiGeneralCoarseOp;
|
||||
|
||||
typedef iVector<CComplex,nbasis > siteVector;
|
||||
typedef iMatrix<CComplex,nbasis > siteMatrix;
|
||||
typedef iVector<SComplex,nbasis > calcVector;
|
||||
typedef iMatrix<SComplex,nbasis > calcMatrix;
|
||||
typedef Lattice<iScalar<CComplex> > CoarseComplexField;
|
||||
typedef Lattice<siteVector> CoarseVector;
|
||||
typedef Lattice<iMatrix<CComplex,nbasis > > CoarseMatrix;
|
||||
typedef iMatrix<CComplex,nbasis > Cobj;
|
||||
typedef iVector<CComplex,nbasis > Cvec;
|
||||
typedef Lattice< CComplex > CoarseScalar; // used for inner products on fine field
|
||||
typedef Lattice<Fobj > FineField;
|
||||
typedef Lattice<CComplex > FineComplexField;
|
||||
typedef CoarseVector Field;
|
||||
|
||||
////////////////////
|
||||
// Data members
|
||||
////////////////////
|
||||
GridCartesian * _CoarseGridMulti;
|
||||
NonLocalStencilGeometry geom;
|
||||
NonLocalStencilGeometry geom_srhs;
|
||||
PaddedCell Cell;
|
||||
GeneralLocalStencil Stencil;
|
||||
|
||||
deviceVector<calcVector> BLAS_B;
|
||||
deviceVector<calcVector> BLAS_C;
|
||||
std::vector<deviceVector<calcMatrix> > BLAS_A;
|
||||
|
||||
std::vector<deviceVector<ComplexD *> > BLAS_AP;
|
||||
std::vector<deviceVector<ComplexD *> > BLAS_BP;
|
||||
deviceVector<ComplexD *> BLAS_CP;
|
||||
|
||||
///////////////////////
|
||||
// Interface
|
||||
///////////////////////
|
||||
GridBase * Grid(void) { return _CoarseGridMulti; }; // this is all the linalg routines need to know
|
||||
GridCartesian * CoarseGrid(void) { return _CoarseGridMulti; }; // this is all the linalg routines need to know
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Accessors shared with DeprecatedGeneralCoarsenedMatrix. Grid() here is the
|
||||
// D+1 multiRHS grid and this class never holds the D dimensional one, so
|
||||
// ExtractMatrix writes into whatever grid the caller's lattice is on.
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
NonLocalStencilGeometry & Geometry(void) { return geom_srhs; };
|
||||
void ExtractMatrix(int p,CoarseMatrix &A) { BLAStoGrid(A,BLAS_A[p]); };
|
||||
|
||||
// One stencil point in or out, matching MultiGeneralCoarsenedOperator, so a
|
||||
// caller can read or write a point without knowing which of the two layouts
|
||||
// (per-point here, site-major there) holds it.
|
||||
void MatrixPointIn(int p,const deviceVector<calcMatrix> &src)
|
||||
{
|
||||
int64_t sites = BLAS_A[p].size();
|
||||
GRID_ASSERT((int64_t)src.size() == sites);
|
||||
calcMatrix *dst = &BLAS_A[p][0];
|
||||
const calcMatrix *sp = &src[0];
|
||||
accelerator_for(ss,sites,1,{ dst[ss] = sp[ss]; });
|
||||
}
|
||||
void MatrixPointOut(int p,deviceVector<calcMatrix> &dst)
|
||||
{
|
||||
int64_t sites = BLAS_A[p].size();
|
||||
dst.resize(sites);
|
||||
const calcMatrix *sp = &BLAS_A[p][0];
|
||||
calcMatrix *dp = &dst[0];
|
||||
accelerator_for(ss,sites,1,{ dp[ss] = sp[ss]; });
|
||||
}
|
||||
|
||||
// I/O on the operator matrices, via the BLAS layout array. The parameter is
|
||||
// a vector over the geometry points; the body indexes A[p].
|
||||
void SetMatrix (int p,std::vector<CoarseMatrix> & A)
|
||||
{
|
||||
GRID_ASSERT(A.size()==geom_srhs.npoint);
|
||||
GridtoBLAS(A[p],BLAS_A[p]);
|
||||
}
|
||||
void GetMatrix (int p,std::vector<CoarseMatrix> & A)
|
||||
{
|
||||
GRID_ASSERT(A.size()==geom_srhs.npoint);
|
||||
BLAStoGrid(A[p],BLAS_A[p]);
|
||||
}
|
||||
void CopyMatrix (GeneralCoarseOp &_Op)
|
||||
{
|
||||
for(int p=0;p<geom.npoint;p++){
|
||||
auto Aup = _Op.Cell.Extract(_Op._A[p]);
|
||||
//Unpadded
|
||||
GridtoBLAS(Aup,BLAS_A[p]);
|
||||
}
|
||||
}
|
||||
/*
|
||||
void CheckMatrix (GeneralCoarseOp &_Op)
|
||||
{
|
||||
std::cout <<"************* Checking the little direc operator mRHS"<<std::endl;
|
||||
for(int p=0;p<geom.npoint;p++){
|
||||
//Unpadded
|
||||
auto Aup = _Op.Cell.Extract(_Op._A[p]);
|
||||
auto Ack = Aup;
|
||||
BLAStoGrid(Ack,BLAS_A[p]);
|
||||
std::cout << p<<" Ack "<<norm2(Ack)<<std::endl;
|
||||
std::cout << p<<" Aup "<<norm2(Aup)<<std::endl;
|
||||
}
|
||||
std::cout <<"************* "<<std::endl;
|
||||
}
|
||||
*/
|
||||
|
||||
DeprecatedMultiGeneralCoarsenedMatrix(NonLocalStencilGeometry &_geom,GridCartesian *CoarseGridMulti) :
|
||||
_CoarseGridMulti(CoarseGridMulti),
|
||||
geom_srhs(_geom),
|
||||
geom(_CoarseGridMulti,_geom.hops,_geom.skip+1),
|
||||
Cell(geom.Depth(),_CoarseGridMulti),
|
||||
Stencil(Cell.grids.back(),geom.shifts) // padded cell stencil
|
||||
{
|
||||
int32_t padded_sites = Cell.grids.back()->lSites();
|
||||
int32_t unpadded_sites = CoarseGridMulti->lSites();
|
||||
|
||||
int32_t nrhs = CoarseGridMulti->FullDimensions()[0]; // # RHS
|
||||
int32_t orhs = nrhs/CComplex::Nsimd();
|
||||
|
||||
padded_sites = padded_sites/nrhs;
|
||||
unpadded_sites = unpadded_sites/nrhs;
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// Device data vector storage
|
||||
/////////////////////////////////////////////////
|
||||
BLAS_A.resize(geom.npoint);
|
||||
for(int p=0;p<geom.npoint;p++){
|
||||
BLAS_A[p].resize (unpadded_sites); // no ghost zone, npoint elements
|
||||
}
|
||||
|
||||
BLAS_B.resize(nrhs *padded_sites); // includes ghost zone
|
||||
BLAS_C.resize(nrhs *unpadded_sites); // no ghost zone
|
||||
BLAS_AP.resize(geom.npoint);
|
||||
BLAS_BP.resize(geom.npoint);
|
||||
for(int p=0;p<geom.npoint;p++){
|
||||
BLAS_AP[p].resize(unpadded_sites);
|
||||
BLAS_BP[p].resize(unpadded_sites);
|
||||
}
|
||||
BLAS_CP.resize(unpadded_sites);
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// Pointers to data
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
// Site identity mapping for A
|
||||
for(int p=0;p<geom.npoint;p++){
|
||||
for(int ss=0;ss<unpadded_sites;ss++){
|
||||
ComplexD *ptr = (ComplexD *)&BLAS_A[p][ss];
|
||||
acceleratorPut(BLAS_AP[p][ss],ptr);
|
||||
}
|
||||
}
|
||||
// Site identity mapping for C
|
||||
for(int ss=0;ss<unpadded_sites;ss++){
|
||||
ComplexD *ptr = (ComplexD *)&BLAS_C[ss*nrhs];
|
||||
acceleratorPut(BLAS_CP[ss],ptr);
|
||||
}
|
||||
|
||||
// Neighbour table is more complicated
|
||||
int32_t j=0; // Interior point counter (unpadded)
|
||||
for(int32_t s=0;s<padded_sites;s++){ // 4 volume, padded
|
||||
int ghost_zone=0;
|
||||
for(int32_t point = 0 ; point < geom.npoint; point++){
|
||||
int i=s*orhs*geom.npoint+point;
|
||||
if( Stencil._entries[i]._wrap ) { // stencil is indexed by the oSite of the CoarseGridMulti, hence orhs factor
|
||||
ghost_zone=1; // If general stencil wrapped in any direction, wrap=1
|
||||
}
|
||||
}
|
||||
|
||||
if( ghost_zone==0) {
|
||||
for(int32_t point = 0 ; point < geom.npoint; point++){
|
||||
int i=s*orhs*geom.npoint+point;
|
||||
int32_t nbr = Stencil._entries[i]._offset*CComplex::Nsimd(); // oSite -> lSite
|
||||
GRID_ASSERT(nbr<BLAS_B.size());
|
||||
ComplexD * ptr = (ComplexD *)&BLAS_B[nbr];
|
||||
acceleratorPut(BLAS_BP[point][j],ptr); // neighbour indexing in ghost zone volume
|
||||
}
|
||||
j++;
|
||||
}
|
||||
}
|
||||
GRID_ASSERT(j==unpadded_sites);
|
||||
}
|
||||
template<class vobj> void GridtoBLAS(const Lattice<vobj> &from,deviceVector<typename vobj::scalar_object> &to)
|
||||
{
|
||||
typedef typename vobj::scalar_object sobj;
|
||||
typedef typename vobj::scalar_type scalar_type;
|
||||
typedef typename vobj::vector_type vector_type;
|
||||
|
||||
GridBase *Fg = from.Grid();
|
||||
GRID_ASSERT(!Fg->_isCheckerBoarded);
|
||||
int nd = Fg->_ndimension;
|
||||
|
||||
to.resize(Fg->lSites());
|
||||
|
||||
Coordinate LocalLatt = Fg->LocalDimensions();
|
||||
size_t nsite = 1;
|
||||
for(int i=0;i<nd;i++) nsite *= LocalLatt[i];
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// do the index calc on the GPU
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Coordinate f_ostride = Fg->_ostride;
|
||||
Coordinate f_istride = Fg->_istride;
|
||||
Coordinate f_rdimensions = Fg->_rdimensions;
|
||||
|
||||
autoView(from_v,from,AcceleratorRead);
|
||||
auto to_v = &to[0];
|
||||
|
||||
const int words=sizeof(vobj)/sizeof(vector_type);
|
||||
accelerator_for(idx,nsite,1,{
|
||||
|
||||
Coordinate from_coor, base;
|
||||
Lexicographic::CoorFromIndex(base,idx,LocalLatt);
|
||||
for(int i=0;i<nd;i++){
|
||||
from_coor[i] = base[i];
|
||||
}
|
||||
int from_oidx = 0; for(int d=0;d<nd;d++) from_oidx+=f_ostride[d]*(from_coor[d]%f_rdimensions[d]);
|
||||
int from_lane = 0; for(int d=0;d<nd;d++) from_lane+=f_istride[d]*(from_coor[d]/f_rdimensions[d]);
|
||||
|
||||
const vector_type* from = (const vector_type *)&from_v[from_oidx];
|
||||
scalar_type* to = (scalar_type *)&to_v[idx];
|
||||
|
||||
scalar_type stmp;
|
||||
for(int w=0;w<words;w++){
|
||||
stmp = getlane(from[w], from_lane);
|
||||
to[w] = stmp;
|
||||
}
|
||||
});
|
||||
}
|
||||
template<class vobj> void BLAStoGrid(Lattice<vobj> &grid,deviceVector<typename vobj::scalar_object> &in)
|
||||
{
|
||||
typedef typename vobj::scalar_object sobj;
|
||||
typedef typename vobj::scalar_type scalar_type;
|
||||
typedef typename vobj::vector_type vector_type;
|
||||
|
||||
GridBase *Tg = grid.Grid();
|
||||
GRID_ASSERT(!Tg->_isCheckerBoarded);
|
||||
int nd = Tg->_ndimension;
|
||||
|
||||
GRID_ASSERT(in.size()==Tg->lSites());
|
||||
|
||||
Coordinate LocalLatt = Tg->LocalDimensions();
|
||||
size_t nsite = 1;
|
||||
for(int i=0;i<nd;i++) nsite *= LocalLatt[i];
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// do the index calc on the GPU
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Coordinate t_ostride = Tg->_ostride;
|
||||
Coordinate t_istride = Tg->_istride;
|
||||
Coordinate t_rdimensions = Tg->_rdimensions;
|
||||
|
||||
autoView(to_v,grid,AcceleratorWrite);
|
||||
auto from_v = &in[0];
|
||||
|
||||
const int words=sizeof(vobj)/sizeof(vector_type);
|
||||
accelerator_for(idx,nsite,1,{
|
||||
|
||||
Coordinate to_coor, base;
|
||||
Lexicographic::CoorFromIndex(base,idx,LocalLatt);
|
||||
for(int i=0;i<nd;i++){
|
||||
to_coor[i] = base[i];
|
||||
}
|
||||
int to_oidx = 0; for(int d=0;d<nd;d++) to_oidx+=t_ostride[d]*(to_coor[d]%t_rdimensions[d]);
|
||||
int to_lane = 0; for(int d=0;d<nd;d++) to_lane+=t_istride[d]*(to_coor[d]/t_rdimensions[d]);
|
||||
|
||||
vector_type* to = (vector_type *)&to_v[to_oidx];
|
||||
scalar_type* from = (scalar_type *)&from_v[idx];
|
||||
|
||||
scalar_type stmp;
|
||||
for(int w=0;w<words;w++){
|
||||
stmp=from[w];
|
||||
putlane(to[w], stmp, to_lane);
|
||||
}
|
||||
});
|
||||
}
|
||||
void CoarsenOperator(LinearOperatorBase<Lattice<Fobj> > &linop,
|
||||
Aggregation<Fobj,CComplex,nbasis> & Subspace,
|
||||
GridBase *CoarseGrid)
|
||||
{
|
||||
#if 0
|
||||
std::cout << GridLogMessage<< "GeneralCoarsenMatrixMrhs "<< std::endl;
|
||||
|
||||
GridBase *grid = Subspace.FineGrid;
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Orthogonalise the subblocks over the basis
|
||||
/////////////////////////////////////////////////////////////
|
||||
CoarseScalar InnerProd(CoarseGrid);
|
||||
blockOrthogonalise(InnerProd,Subspace.subspace);
|
||||
|
||||
const int npoint = geom_srhs.npoint;
|
||||
|
||||
Coordinate clatt = CoarseGrid->GlobalDimensions();
|
||||
int Nd = CoarseGrid->Nd();
|
||||
/*
|
||||
* Here, k,l index which possible momentum/shift within the N-points connected by MdagM.
|
||||
* Matrix index i is mapped to this shift via
|
||||
* geom.shifts[i]
|
||||
*
|
||||
* conj(pha[block]) proj[k (which mom)][j (basis vec cpt)][block]
|
||||
* = \sum_{l in ball} e^{i q_k . delta_l} < phi_{block,j} | MdagM | phi_{(block+delta_l),i} >
|
||||
* = \sum_{l in ball} e^{iqk.delta_l} A_ji^{b.b+l}
|
||||
* = M_{kl} A_ji^{b.b+l}
|
||||
*
|
||||
* Must assemble and invert matrix M_k,l = e^[i q_k . delta_l]
|
||||
*
|
||||
* Where q_k = delta_k . (2*M_PI/global_nb[mu])
|
||||
*
|
||||
* Then A{ji}^{b,b+l} = M^{-1}_{lm} ComputeProj_{m,b,i,j}
|
||||
*/
|
||||
Eigen::MatrixXcd Mkl = Eigen::MatrixXcd::Zero(npoint,npoint);
|
||||
Eigen::MatrixXcd invMkl = Eigen::MatrixXcd::Zero(npoint,npoint);
|
||||
ComplexD ci(0.0,1.0);
|
||||
for(int k=0;k<npoint;k++){ // Loop over momenta
|
||||
|
||||
for(int l=0;l<npoint;l++){ // Loop over nbr relative
|
||||
ComplexD phase(0.0,0.0);
|
||||
for(int mu=0;mu<Nd;mu++){
|
||||
RealD TwoPiL = M_PI * 2.0/ clatt[mu];
|
||||
phase=phase+TwoPiL*geom_srhs.shifts[k][mu]*geom_srhs.shifts[l][mu];
|
||||
}
|
||||
phase=exp(phase*ci);
|
||||
Mkl(k,l) = phase;
|
||||
}
|
||||
}
|
||||
invMkl = Mkl.inverse();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Now compute the matrix elements of linop between the orthonormal
|
||||
// set of vectors.
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
FineField phaV(grid); // Phased block basis vector
|
||||
FineField MphaV(grid);// Matrix applied
|
||||
std::vector<FineComplexField> phaF(npoint,grid);
|
||||
std::vector<CoarseComplexField> pha(npoint,CoarseGrid);
|
||||
|
||||
CoarseVector coarseInner(CoarseGrid);
|
||||
|
||||
typedef typename CComplex::scalar_type SComplex;
|
||||
FineComplexField one(grid); one=SComplex(1.0);
|
||||
FineComplexField zz(grid); zz = Zero();
|
||||
for(int p=0;p<npoint;p++){ // Loop over momenta in npoint
|
||||
/////////////////////////////////////////////////////
|
||||
// Stick a phase on every block
|
||||
/////////////////////////////////////////////////////
|
||||
CoarseComplexField coor(CoarseGrid);
|
||||
pha[p]=Zero();
|
||||
for(int mu=0;mu<Nd;mu++){
|
||||
LatticeCoordinate(coor,mu);
|
||||
RealD TwoPiL = M_PI * 2.0/ clatt[mu];
|
||||
pha[p] = pha[p] + (TwoPiL * geom_srhs.shifts[p][mu]) * coor;
|
||||
}
|
||||
pha[p] =exp(pha[p]*ci);
|
||||
|
||||
blockZAXPY(phaF[p],pha[p],one,zz);
|
||||
}
|
||||
|
||||
// Could save on temporary storage here
|
||||
std::vector<CoarseMatrix> _A;
|
||||
_A.resize(geom_srhs.npoint,CoarseGrid);
|
||||
|
||||
std::vector<CoarseVector> ComputeProj(npoint,CoarseGrid);
|
||||
CoarseVector FT(CoarseGrid);
|
||||
for(int i=0;i<nbasis;i++){// Loop over basis vectors
|
||||
std::cout << GridLogMessage<< "CoarsenMatrixColoured vec "<<i<<"/"<<nbasis<< std::endl;
|
||||
for(int p=0;p<npoint;p++){ // Loop over momenta in npoint
|
||||
|
||||
phaV = phaF[p]*Subspace.subspace[i];
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Multiple phased subspace vector by matrix and project to subspace
|
||||
// Remove local bulk phase to leave relative phases
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
linop.Op(phaV,MphaV);
|
||||
|
||||
// Fixme, could use batched block projector here
|
||||
blockProject(coarseInner,MphaV,Subspace.subspace);
|
||||
|
||||
coarseInner = conjugate(pha[p]) * coarseInner;
|
||||
|
||||
ComputeProj[p] = coarseInner;
|
||||
}
|
||||
|
||||
// Could do this with a block promote or similar BLAS call via the MultiRHSBlockProjector with a const matrix.
|
||||
for(int k=0;k<npoint;k++){
|
||||
|
||||
FT = Zero();
|
||||
for(int l=0;l<npoint;l++){
|
||||
FT= FT+ invMkl(l,k)*ComputeProj[l];
|
||||
}
|
||||
|
||||
int osites=CoarseGrid->oSites();
|
||||
autoView( A_v , _A[k], AcceleratorWrite);
|
||||
autoView( FT_v , FT, AcceleratorRead);
|
||||
accelerator_for(sss, osites, 1, {
|
||||
for(int j=0;j<nbasis;j++){
|
||||
A_v[sss](i,j) = FT_v[sss](j);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Only needed if nonhermitian
|
||||
// if ( ! hermitian ) {
|
||||
// std::cout << GridLogMessage<<"PopulateAdag "<<std::endl;
|
||||
// PopulateAdag();
|
||||
// }
|
||||
// Need to write something to populate Adag from A
|
||||
|
||||
for(int p=0;p<geom_srhs.npoint;p++){
|
||||
GridtoBLAS(_A[p],BLAS_A[p]);
|
||||
}
|
||||
/*
|
||||
Grid : Message : 11698.730546 s : CoarsenOperator eigen 1334 us
|
||||
Grid : Message : 11698.730563 s : CoarsenOperator phase 34729 us
|
||||
Grid : Message : 11698.730565 s : CoarsenOperator phaseBZ 2423814 us
|
||||
Grid : Message : 11698.730566 s : CoarsenOperator mat 127890998 us
|
||||
Grid : Message : 11698.730567 s : CoarsenOperator proj 515840840 us
|
||||
Grid : Message : 11698.730568 s : CoarsenOperator inv 103948313 us
|
||||
Takes 600s to compute matrix elements, DOMINATED by the block project.
|
||||
Easy to speed up with the batched block project.
|
||||
Store npoint vectors, get npoint x Nbasis block projection, and 81 fold faster.
|
||||
|
||||
// Block project below taks to 240s
|
||||
Grid : Message : 328.193418 s : CoarsenOperator phase 38338 us
|
||||
Grid : Message : 328.193434 s : CoarsenOperator phaseBZ 1711226 us
|
||||
Grid : Message : 328.193436 s : CoarsenOperator mat 122213270 us
|
||||
//Grid : Message : 328.193438 s : CoarsenOperator proj 1181154 us <-- this is mistimed
|
||||
//Grid : Message : 11698.730568 s : CoarsenOperator inv 103948313 us <-- Cut this ~10x if lucky by loop fusion
|
||||
*/
|
||||
#else
|
||||
RealD tproj=0.0;
|
||||
RealD tmat=0.0;
|
||||
RealD tphase=0.0;
|
||||
RealD tphaseBZ=0.0;
|
||||
RealD tinv=0.0;
|
||||
|
||||
std::cout << GridLogMessage<< "GeneralCoarsenMatrixMrhs "<< std::endl;
|
||||
|
||||
GridBase *grid = Subspace.FineGrid;
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// Orthogonalise the subblocks over the basis
|
||||
/////////////////////////////////////////////////////////////
|
||||
CoarseScalar InnerProd(CoarseGrid);
|
||||
blockOrthogonalise(InnerProd,Subspace.subspace);
|
||||
|
||||
|
||||
MultiRHSBlockProject<Lattice<Fobj> > Projector;
|
||||
Projector.Allocate(nbasis,grid,CoarseGrid);
|
||||
Projector.ImportBasis(Subspace.subspace);
|
||||
|
||||
const int npoint = geom_srhs.npoint;
|
||||
|
||||
Coordinate clatt = CoarseGrid->GlobalDimensions();
|
||||
int Nd = CoarseGrid->Nd();
|
||||
/*
|
||||
* Here, k,l index which possible momentum/shift within the N-points connected by MdagM.
|
||||
* Matrix index i is mapped to this shift via
|
||||
* geom.shifts[i]
|
||||
*
|
||||
* conj(pha[block]) proj[k (which mom)][j (basis vec cpt)][block]
|
||||
* = \sum_{l in ball} e^{i q_k . delta_l} < phi_{block,j} | MdagM | phi_{(block+delta_l),i} >
|
||||
* = \sum_{l in ball} e^{iqk.delta_l} A_ji^{b.b+l}
|
||||
* = M_{kl} A_ji^{b.b+l}
|
||||
*
|
||||
* Must assemble and invert matrix M_k,l = e^[i q_k . delta_l]
|
||||
*
|
||||
* Where q_k = delta_k . (2*M_PI/global_nb[mu])
|
||||
*
|
||||
* Then A{ji}^{b,b+l} = M^{-1}_{lm} ComputeProj_{m,b,i,j}
|
||||
*/
|
||||
Eigen::MatrixXcd Mkl = Eigen::MatrixXcd::Zero(npoint,npoint);
|
||||
Eigen::MatrixXcd invMkl = Eigen::MatrixXcd::Zero(npoint,npoint);
|
||||
ComplexD ci(0.0,1.0);
|
||||
for(int k=0;k<npoint;k++){ // Loop over momenta
|
||||
|
||||
for(int l=0;l<npoint;l++){ // Loop over nbr relative
|
||||
ComplexD phase(0.0,0.0);
|
||||
for(int mu=0;mu<Nd;mu++){
|
||||
RealD TwoPiL = M_PI * 2.0/ clatt[mu];
|
||||
phase=phase+TwoPiL*geom_srhs.shifts[k][mu]*geom_srhs.shifts[l][mu];
|
||||
}
|
||||
phase=exp(phase*ci);
|
||||
Mkl(k,l) = phase;
|
||||
}
|
||||
}
|
||||
invMkl = Mkl.inverse();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// Now compute the matrix elements of linop between the orthonormal
|
||||
// set of vectors.
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
FineField phaV(grid); // Phased block basis vector
|
||||
FineField MphaV(grid);// Matrix applied
|
||||
std::vector<FineComplexField> phaF(npoint,grid);
|
||||
std::vector<CoarseComplexField> pha(npoint,CoarseGrid);
|
||||
|
||||
CoarseVector coarseInner(CoarseGrid);
|
||||
|
||||
tphase=-usecond();
|
||||
typedef typename CComplex::scalar_type SComplex;
|
||||
FineComplexField one(grid); one=SComplex(1.0);
|
||||
FineComplexField zz(grid); zz = Zero();
|
||||
for(int p=0;p<npoint;p++){ // Loop over momenta in npoint
|
||||
/////////////////////////////////////////////////////
|
||||
// Stick a phase on every block
|
||||
/////////////////////////////////////////////////////
|
||||
CoarseComplexField coor(CoarseGrid);
|
||||
pha[p]=Zero();
|
||||
for(int mu=0;mu<Nd;mu++){
|
||||
LatticeCoordinate(coor,mu);
|
||||
RealD TwoPiL = M_PI * 2.0/ clatt[mu];
|
||||
pha[p] = pha[p] + (TwoPiL * geom_srhs.shifts[p][mu]) * coor;
|
||||
}
|
||||
pha[p] =exp(pha[p]*ci);
|
||||
|
||||
blockZAXPY(phaF[p],pha[p],one,zz);
|
||||
}
|
||||
tphase+=usecond();
|
||||
|
||||
// Could save on temporary storage here
|
||||
std::vector<CoarseMatrix> _A;
|
||||
_A.resize(geom_srhs.npoint,CoarseGrid);
|
||||
|
||||
// Count use small chunks than npoint == 81 and save memory
|
||||
int batch = 9;
|
||||
std::vector<FineField> _MphaV(batch,grid);
|
||||
std::vector<CoarseVector> TmpProj(batch,CoarseGrid);
|
||||
|
||||
std::vector<CoarseVector> ComputeProj(npoint,CoarseGrid);
|
||||
CoarseVector FT(CoarseGrid);
|
||||
for(int i=0;i<nbasis;i++){// Loop over basis vectors
|
||||
std::cout << GridLogMessage<< "CoarsenMatrixColoured vec "<<i<<"/"<<nbasis<< std::endl;
|
||||
|
||||
// std::cout << GridLogMessage << " phasing the fine vector "<<std::endl;
|
||||
// Fixme : do this in batches
|
||||
for(int p=0;p<npoint;p+=batch){ // Loop over momenta in npoint
|
||||
|
||||
for(int b=0;b<MIN(batch,npoint-p);b++){
|
||||
tphaseBZ-=usecond();
|
||||
phaV = phaF[p+b]*Subspace.subspace[i];
|
||||
tphaseBZ+=usecond();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Multiple phased subspace vector by matrix and project to subspace
|
||||
// Remove local bulk phase to leave relative phases
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Memory footprint was an issue
|
||||
tmat-=usecond();
|
||||
linop.Op(phaV,MphaV);
|
||||
_MphaV[b] = MphaV;
|
||||
tmat+=usecond();
|
||||
}
|
||||
|
||||
// std::cout << GridLogMessage << " Calling block project "<<std::endl;
|
||||
tproj-=usecond();
|
||||
Projector.blockProject(_MphaV,TmpProj);
|
||||
tproj+=usecond();
|
||||
|
||||
// std::cout << GridLogMessage << " conj phasing the coarse vectors "<<std::endl;
|
||||
for(int b=0;b<MIN(batch,npoint-p);b++){
|
||||
ComputeProj[p+b] = conjugate(pha[p+b])*TmpProj[b];
|
||||
}
|
||||
}
|
||||
|
||||
// Could do this with a block promote or similar BLAS call via the MultiRHSBlockProjector with a const matrix.
|
||||
|
||||
// std::cout << GridLogMessage << " Starting FT inv "<<std::endl;
|
||||
tinv-=usecond();
|
||||
for(int k=0;k<npoint;k++){
|
||||
FT = Zero();
|
||||
// 81 kernel calls as many ComputeProj vectors
|
||||
// Could fuse with a vector of views, but ugly
|
||||
// Could unroll the expression and run fewer kernels -- much more attractive
|
||||
// Could also do non blocking.
|
||||
#if 0
|
||||
for(int l=0;l<npoint;l++){
|
||||
FT= FT+ invMkl(l,k)*ComputeProj[l];
|
||||
}
|
||||
#else
|
||||
const int radix = 9;
|
||||
int ll;
|
||||
for(ll=0;ll+radix-1<npoint;ll+=radix){
|
||||
// When ll = npoint-radix, ll+radix-1 = npoint-1, and we do it all.
|
||||
FT = FT
|
||||
+ invMkl(ll+0,k)*ComputeProj[ll+0]
|
||||
+ invMkl(ll+1,k)*ComputeProj[ll+1]
|
||||
+ invMkl(ll+2,k)*ComputeProj[ll+2]
|
||||
+ invMkl(ll+3,k)*ComputeProj[ll+3]
|
||||
+ invMkl(ll+4,k)*ComputeProj[ll+4]
|
||||
+ invMkl(ll+5,k)*ComputeProj[ll+5]
|
||||
+ invMkl(ll+6,k)*ComputeProj[ll+6]
|
||||
+ invMkl(ll+7,k)*ComputeProj[ll+7]
|
||||
+ invMkl(ll+8,k)*ComputeProj[ll+8];
|
||||
}
|
||||
for(int l=ll;l<npoint;l++){
|
||||
FT= FT+ invMkl(l,k)*ComputeProj[l];
|
||||
}
|
||||
#endif
|
||||
|
||||
// 1 kernel call -- must be cheaper
|
||||
int osites=CoarseGrid->oSites();
|
||||
autoView( A_v , _A[k], AcceleratorWrite);
|
||||
autoView( FT_v , FT, AcceleratorRead);
|
||||
accelerator_for(sss, osites, 1, {
|
||||
for(int j=0;j<nbasis;j++){
|
||||
A_v[sss](i,j) = FT_v[sss](j);
|
||||
}
|
||||
});
|
||||
}
|
||||
tinv+=usecond();
|
||||
}
|
||||
|
||||
// Only needed if nonhermitian
|
||||
// if ( ! hermitian ) {
|
||||
// std::cout << GridLogMessage<<"PopulateAdag "<<std::endl;
|
||||
// PopulateAdag();
|
||||
// }
|
||||
// Need to write something to populate Adag from A
|
||||
// std::cout << GridLogMessage << " Calling GridtoBLAS "<<std::endl;
|
||||
for(int p=0;p<geom_srhs.npoint;p++){
|
||||
GridtoBLAS(_A[p],BLAS_A[p]);
|
||||
}
|
||||
std::cout << GridLogMessage<<"CoarsenOperator phase "<<tphase<<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"CoarsenOperator phaseBZ "<<tphaseBZ<<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"CoarsenOperator mat "<<tmat <<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"CoarsenOperator proj "<<tproj<<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"CoarsenOperator inv "<<tinv<<" us"<<std::endl;
|
||||
#endif
|
||||
}
|
||||
void Mdag(const CoarseVector &in, CoarseVector &out)
|
||||
{
|
||||
this->M(in,out);
|
||||
}
|
||||
void M (const CoarseVector &in, CoarseVector &out)
|
||||
{
|
||||
// std::cout << GridLogMessage << "New Mrhs coarse"<<std::endl;
|
||||
conformable(CoarseGrid(),in.Grid());
|
||||
conformable(in.Grid(),out.Grid());
|
||||
out.Checkerboard() = in.Checkerboard();
|
||||
|
||||
RealD t_tot;
|
||||
RealD t_exch;
|
||||
RealD t_GtoB;
|
||||
RealD t_BtoG;
|
||||
RealD t_mult;
|
||||
|
||||
t_tot=-usecond();
|
||||
CoarseVector tin=in;
|
||||
t_exch=-usecond();
|
||||
CoarseVector pin = Cell.ExchangePeriodic(tin); //padded input
|
||||
t_exch+=usecond();
|
||||
|
||||
CoarseVector pout(pin.Grid());
|
||||
|
||||
int npoint = geom.npoint;
|
||||
typedef calcMatrix* Aview;
|
||||
typedef LatticeView<Cvec> Vview;
|
||||
|
||||
const int Nsimd = CComplex::Nsimd();
|
||||
|
||||
int64_t nrhs =pin.Grid()->GlobalDimensions()[0];
|
||||
GRID_ASSERT(nrhs>=1);
|
||||
|
||||
RealD flops,bytes;
|
||||
int64_t osites=in.Grid()->oSites(); // unpadded
|
||||
int64_t unpadded_vol = CoarseGrid()->lSites()/nrhs;
|
||||
|
||||
flops = 1.0* npoint * nbasis * nbasis * 8.0 * osites * CComplex::Nsimd();
|
||||
bytes = 1.0*osites*sizeof(siteMatrix)*npoint/pin.Grid()->GlobalDimensions()[0]
|
||||
+ 2.0*osites*sizeof(siteVector)*npoint;
|
||||
|
||||
|
||||
t_GtoB=-usecond();
|
||||
GridtoBLAS(pin,BLAS_B);
|
||||
t_GtoB+=usecond();
|
||||
|
||||
GridBLAS BLAS;
|
||||
|
||||
t_mult=-usecond();
|
||||
for(int p=0;p<geom.npoint;p++){
|
||||
RealD c = 1.0;
|
||||
if (p==0) c = 0.0;
|
||||
ComplexD beta(c);
|
||||
|
||||
BLAS.gemmBatched(nbasis,nrhs,nbasis,
|
||||
ComplexD(1.0),
|
||||
BLAS_AP[p],
|
||||
BLAS_BP[p],
|
||||
ComplexD(c),
|
||||
BLAS_CP);
|
||||
}
|
||||
BLAS.synchronise();
|
||||
t_mult+=usecond();
|
||||
|
||||
t_BtoG=-usecond();
|
||||
BLAStoGrid(out,BLAS_C);
|
||||
t_BtoG+=usecond();
|
||||
t_tot+=usecond();
|
||||
/*
|
||||
std::cout << GridLogMessage << "New Mrhs coarse DONE "<<std::endl;
|
||||
std::cout << GridLogMessage<<"Coarse Mult exch "<<t_exch<<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"Coarse Mult mult "<<t_mult<<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"Coarse Mult GtoB "<<t_GtoB<<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"Coarse Mult BtoG "<<t_BtoG<<" us"<<std::endl;
|
||||
std::cout << GridLogMessage<<"Coarse Mult tot "<<t_tot<<" us"<<std::endl;
|
||||
*/
|
||||
// std::cout << GridLogMessage<<std::endl;
|
||||
// std::cout << GridLogMessage<<"Coarse Kernel flops "<< flops<<std::endl;
|
||||
// std::cout << GridLogMessage<<"Coarse Kernel flop/s "<< flops/t_mult<<" mflop/s"<<std::endl;
|
||||
// std::cout << GridLogMessage<<"Coarse Kernel bytes/s "<< bytes/t_mult/1000<<" GB/s"<<std::endl;
|
||||
// std::cout << GridLogMessage<<"Coarse overall flops/s "<< flops/t_tot<<" mflop/s"<<std::endl;
|
||||
// std::cout << GridLogMessage<<"Coarse total bytes "<< bytes/1e6<<" MB"<<std::endl;
|
||||
};
|
||||
virtual void Mdiag (const Field &in, Field &out){ GRID_ASSERT(0);};
|
||||
virtual void Mdir (const Field &in, Field &out,int dir, int disp){assert(0);};
|
||||
virtual void MdirAll (const Field &in, std::vector<Field> &out){assert(0);};
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -0,0 +1,136 @@
|
||||
/*************************************************************************************
|
||||
|
||||
Grid physics library, www.github.com/paboyle/Grid
|
||||
|
||||
Source file: ./Grid/algorithms/multigrid/deprecated/TwoLevelADEF2.h
|
||||
|
||||
Copyright (C) 2015
|
||||
|
||||
Author: Peter Boyle <paboyle@ph.ed.ac.uk>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
See the full license in the file "LICENSE" in the top level distribution directory
|
||||
*************************************************************************************/
|
||||
/* END LEGAL */
|
||||
#pragma once
|
||||
|
||||
// DEPRECATED with the other classes in deprecated/: the single-RHS ADEF-2 on an
|
||||
// Aggregation (ProjectToSubspace/PromoteFromSubspace) and a D-dimensional
|
||||
// coarse operator. The mrhs solver in AdefMrhs.h covers one right-hand
|
||||
// side through its LinearFunction interface, on MultiGeneralCoarsenedOperator.
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
template<class Field, class CoarseField, class Aggregation>
|
||||
class DeprecatedTwoLevelADEF2 : public TwoLevelCG<Field>
|
||||
{
|
||||
public:
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Need something that knows how to get from Coarse to fine and back again
|
||||
// void ProjectToSubspace(CoarseVector &CoarseVec,const FineField &FineVec){
|
||||
// void PromoteFromSubspace(const CoarseVector &CoarseVec,FineField &FineVec){
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
GridBase *coarsegrid;
|
||||
Aggregation &_Aggregates;
|
||||
LinearFunction<CoarseField> &_CoarseSolver;
|
||||
LinearFunction<CoarseField> &_CoarseSolverPrecise;
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// more most opertor functions
|
||||
DeprecatedTwoLevelADEF2(RealD tol,
|
||||
Integer maxit,
|
||||
LinearOperatorBase<Field> &FineLinop,
|
||||
LinearFunction<Field> &Smoother,
|
||||
LinearFunction<CoarseField> &CoarseSolver,
|
||||
LinearFunction<CoarseField> &CoarseSolverPrecise,
|
||||
Aggregation &Aggregates
|
||||
) :
|
||||
TwoLevelCG<Field>(tol,maxit,FineLinop,Smoother,Aggregates.FineGrid),
|
||||
_CoarseSolver(CoarseSolver),
|
||||
_CoarseSolverPrecise(CoarseSolverPrecise),
|
||||
_Aggregates(Aggregates)
|
||||
{
|
||||
coarsegrid = Aggregates.CoarseGrid;
|
||||
};
|
||||
|
||||
virtual void PcgM1(Field & in, Field & out)
|
||||
{
|
||||
GRID_TRACE("MultiGridPreconditioner ");
|
||||
// [PTM+Q] in = [1 - Q A] M in + Q in = Min + Q [ in -A Min]
|
||||
|
||||
Field tmp(this->grid);
|
||||
Field Min(this->grid);
|
||||
CoarseField PleftProj(this->coarsegrid);
|
||||
CoarseField PleftMss_proj(this->coarsegrid);
|
||||
|
||||
this->SmoothTimer.Start();
|
||||
this->_Smoother(in,Min);
|
||||
this->SmoothTimer.Stop();
|
||||
this->SmoothCalls++;
|
||||
|
||||
this->MatrixTimer.Start();
|
||||
this->_FineLinop.HermOp(Min,out);
|
||||
this->MatrixTimer.Stop();
|
||||
this->MatrixCalls++;
|
||||
axpy(tmp,-1.0,out,in); // tmp = in - A Min
|
||||
|
||||
this->ProjectTimer.Start();
|
||||
this->_Aggregates.ProjectToSubspace(PleftProj,tmp);
|
||||
this->ProjectTimer.Stop();
|
||||
this->ProjectCalls++;
|
||||
this->CoarseTimer.Start();
|
||||
this->_CoarseSolver(PleftProj,PleftMss_proj); // Ass^{-1} [in - A Min]_s
|
||||
this->CoarseTimer.Stop();
|
||||
this->CoarseCalls++;
|
||||
this->PromoteTimer.Start();
|
||||
this->_Aggregates.PromoteFromSubspace(PleftMss_proj,tmp);// tmp = Q[in - A Min]
|
||||
this->PromoteTimer.Stop();
|
||||
this->PromoteCalls++;
|
||||
|
||||
axpy(out,1.0,Min,tmp); // Min+tmp
|
||||
}
|
||||
|
||||
virtual void Vstart(Field & x,const Field & src)
|
||||
{
|
||||
std::cout << GridLogMessage<<"HDCG: fPcg Vstart "<<std::endl;
|
||||
///////////////////////////////////
|
||||
// Choose x_0 such that
|
||||
// x_0 = guess + (A_ss^inv) r_s = guess + Ass_inv [src -Aguess]
|
||||
// = [1 - Ass_inv A] Guess + Assinv src
|
||||
// = P^T guess + Assinv src
|
||||
// = Vstart [Tang notation]
|
||||
// This gives:
|
||||
// W^T (src - A x_0) = src_s - A guess_s - r_s
|
||||
// = src_s - (A guess)_s - src_s + (A guess)_s
|
||||
// = 0
|
||||
///////////////////////////////////
|
||||
Field r(this->grid);
|
||||
Field mmp(this->grid);
|
||||
CoarseField PleftProj(this->coarsegrid);
|
||||
CoarseField PleftMss_proj(this->coarsegrid);
|
||||
|
||||
std::cout << GridLogMessage<<"HDCG: fPcg Vstart projecting "<<std::endl;
|
||||
this->_Aggregates.ProjectToSubspace(PleftProj,src);
|
||||
std::cout << GridLogMessage<<"HDCG: fPcg Vstart coarse solve "<<std::endl;
|
||||
this->_CoarseSolverPrecise(PleftProj,PleftMss_proj); // Ass^{-1} r_s
|
||||
std::cout << GridLogMessage<<"HDCG: fPcg Vstart promote "<<std::endl;
|
||||
this->_Aggregates.PromoteFromSubspace(PleftMss_proj,x);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
@@ -54,7 +54,10 @@ public:
|
||||
size_type bytes = __n*sizeof(_Tp);
|
||||
profilerAllocate(bytes);
|
||||
_Tp *ptr = (_Tp*) MemoryManager::CpuAllocate(bytes);
|
||||
assert( ( (_Tp*)ptr != (_Tp *)NULL ) );
|
||||
if ( (_Tp*)ptr == (_Tp *) NULL ) {
|
||||
printf("Grid CPU Allocator got NULL for %lu bytes\n",(unsigned long) bytes );
|
||||
}
|
||||
GRID_ASSERT( ( (_Tp*)ptr != (_Tp *)NULL ) );
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@@ -66,7 +69,7 @@ public:
|
||||
}
|
||||
|
||||
// FIXME: hack for the copy constructor: it must be avoided to avoid single thread loop
|
||||
void construct(pointer __p, const _Tp& __val) { assert(0);};
|
||||
void construct(pointer __p, const _Tp& __val) { };
|
||||
void construct(pointer __p) { };
|
||||
void destroy(pointer __p) { };
|
||||
};
|
||||
@@ -100,7 +103,10 @@ public:
|
||||
size_type bytes = __n*sizeof(_Tp);
|
||||
profilerAllocate(bytes);
|
||||
_Tp *ptr = (_Tp*) MemoryManager::SharedAllocate(bytes);
|
||||
assert( ( (_Tp*)ptr != (_Tp *)NULL ) );
|
||||
if ( (_Tp*)ptr == (_Tp *) NULL ) {
|
||||
printf("Grid Shared Allocator got NULL for %lu bytes\n",(unsigned long) bytes );
|
||||
}
|
||||
GRID_ASSERT( ( (_Tp*)ptr != (_Tp *)NULL ) );
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@@ -145,7 +151,10 @@ public:
|
||||
size_type bytes = __n*sizeof(_Tp);
|
||||
profilerAllocate(bytes);
|
||||
_Tp *ptr = (_Tp*) MemoryManager::AcceleratorAllocate(bytes);
|
||||
assert( ( (_Tp*)ptr != (_Tp *)NULL ) );
|
||||
if ( (_Tp*)ptr == (_Tp *) NULL ) {
|
||||
printf("Grid Device Allocator got NULL for %lu bytes\n",(unsigned long) bytes );
|
||||
}
|
||||
GRID_ASSERT( ( (_Tp*)ptr != (_Tp *)NULL ) );
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@@ -165,19 +174,48 @@ template<typename _Tp> inline bool operator!=(const devAllocator<_Tp>&, const d
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Template typedefs
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
#ifdef ACCELERATOR_CSHIFT
|
||||
// Cshift on device
|
||||
template<class T> using cshiftAllocator = devAllocator<T>;
|
||||
#else
|
||||
// Cshift on host
|
||||
template<class T> using cshiftAllocator = std::allocator<T>;
|
||||
#endif
|
||||
template<class T> using hostVector = std::vector<T,alignedAllocator<T> >; // Needs autoview
|
||||
template<class T> using Vector = std::vector<T,uvmAllocator<T> >; // Really want to deprecate
|
||||
template<class T> using uvmVector = std::vector<T,uvmAllocator<T> >; // auto migrating page
|
||||
template<class T> using deviceVector = std::vector<T,devAllocator<T> >; // device vector
|
||||
|
||||
template<class T> using Vector = std::vector<T,uvmAllocator<T> >;
|
||||
template<class T> using stencilVector = std::vector<T,alignedAllocator<T> >;
|
||||
template<class T> using commVector = std::vector<T,devAllocator<T> >;
|
||||
template<class T> using deviceVector = std::vector<T,devAllocator<T> >;
|
||||
template<class T> using cshiftVector = std::vector<T,cshiftAllocator<T> >;
|
||||
/*
|
||||
template<class T> class vecView
|
||||
{
|
||||
protected:
|
||||
T * data;
|
||||
uint64_t size;
|
||||
ViewMode mode;
|
||||
void * cpu_ptr;
|
||||
public:
|
||||
// Rvalue accessor
|
||||
accelerator_inline T & operator[](size_t i) const { return this->data[i]; };
|
||||
vecView(Vector<T> &refer_to_me,ViewMode _mode)
|
||||
{
|
||||
cpu_ptr = &refer_to_me[0];
|
||||
size = refer_to_me.size();
|
||||
mode = _mode;
|
||||
data =(T *) MemoryManager::ViewOpen(cpu_ptr,
|
||||
size*sizeof(T),
|
||||
mode,
|
||||
AdviseDefault);
|
||||
}
|
||||
void ViewClose(void)
|
||||
{ // Inform the manager
|
||||
MemoryManager::ViewClose(this->cpu_ptr,this->mode);
|
||||
}
|
||||
};
|
||||
|
||||
template<class T> vecView<T> VectorView(Vector<T> &vec,ViewMode _mode)
|
||||
{
|
||||
vecView<T> ret(vec,_mode); // does the open
|
||||
return ret; // must be closed
|
||||
}
|
||||
|
||||
#define autoVecView(v_v,v,mode) \
|
||||
auto v_v = VectorView(v,mode); \
|
||||
ViewCloser<decltype(v_v)> _autoView##v_v(v_v);
|
||||
*/
|
||||
|
||||
NAMESPACE_END(Grid);
|
||||
|
||||
|
||||
+125
-15
@@ -16,6 +16,44 @@ NAMESPACE_BEGIN(Grid);
|
||||
uint64_t total_shared;
|
||||
uint64_t total_device;
|
||||
uint64_t total_host;;
|
||||
|
||||
#if defined(__has_feature)
|
||||
#if __has_feature(leak_sanitizer)
|
||||
#define ASAN_LEAK_CHECK
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef ASAN_LEAK_CHECK
|
||||
#include <sanitizer/asan_interface.h>
|
||||
#include <sanitizer/common_interface_defs.h>
|
||||
#include <sanitizer/lsan_interface.h>
|
||||
#define LEAK_CHECK(A) { __lsan_do_recoverable_leak_check(); }
|
||||
#else
|
||||
#define LEAK_CHECK(A) { }
|
||||
#endif
|
||||
|
||||
void MemoryManager::DisplayMallinfo(void)
|
||||
{
|
||||
#ifdef __linux__
|
||||
struct mallinfo mi; // really want mallinfo2, but glibc version isn't uniform
|
||||
|
||||
mi = mallinfo();
|
||||
|
||||
std::cout << "MemoryManager: Total non-mmapped bytes (arena): "<< (size_t)mi.arena<<std::endl;
|
||||
std::cout << "MemoryManager: # of free chunks (ordblks): "<< (size_t)mi.ordblks<<std::endl;
|
||||
std::cout << "MemoryManager: # of free fastbin blocks (smblks): "<< (size_t)mi.smblks<<std::endl;
|
||||
std::cout << "MemoryManager: # of mapped regions (hblks): "<< (size_t)mi.hblks<<std::endl;
|
||||
std::cout << "MemoryManager: Bytes in mapped regions (hblkhd): "<< (size_t)mi.hblkhd<<std::endl;
|
||||
std::cout << "MemoryManager: Max. total allocated space (usmblks): "<< (size_t)mi.usmblks<<std::endl;
|
||||
std::cout << "MemoryManager: Free bytes held in fastbins (fsmblks): "<< (size_t)mi.fsmblks<<std::endl;
|
||||
std::cout << "MemoryManager: Total allocated space (uordblks): "<< (size_t)mi.uordblks<<std::endl;
|
||||
std::cout << "MemoryManager: Total free space (fordblks): "<< (size_t)mi.fordblks<<std::endl;
|
||||
std::cout << "MemoryManager: Topmost releasable block (keepcost): "<< (size_t)mi.keepcost<<std::endl;
|
||||
#endif
|
||||
LEAK_CHECK();
|
||||
|
||||
}
|
||||
|
||||
void MemoryManager::PrintBytes(void)
|
||||
{
|
||||
std::cout << " MemoryManager : ------------------------------------ "<<std::endl;
|
||||
@@ -25,17 +63,15 @@ void MemoryManager::PrintBytes(void)
|
||||
std::cout << " MemoryManager : "<<(total_device>>20)<<" accelerator Mbytes "<<std::endl;
|
||||
std::cout << " MemoryManager : "<<(total_host>>20) <<" cpu Mbytes "<<std::endl;
|
||||
uint64_t cacheBytes;
|
||||
cacheBytes = CacheBytes[Cpu];
|
||||
std::cout << " MemoryManager : "<<(cacheBytes>>20) <<" cpu cache Mbytes "<<std::endl;
|
||||
cacheBytes = CacheBytes[Acc];
|
||||
std::cout << " MemoryManager : "<<(cacheBytes>>20) <<" acc cache Mbytes "<<std::endl;
|
||||
cacheBytes = CacheBytes[Shared];
|
||||
std::cout << " MemoryManager : "<<(cacheBytes>>20) <<" shared cache Mbytes "<<std::endl;
|
||||
cacheBytes = HostCacheBytes();
|
||||
std::cout << " MemoryManager : "<<(cacheBytes>>20) <<" cpu alloc cache Mbytes "<<std::endl;
|
||||
cacheBytes = DeviceCacheBytes();
|
||||
std::cout << " MemoryManager : "<<(cacheBytes>>20) <<" acc alloc cache Mbytes "<<std::endl;
|
||||
|
||||
#ifdef GRID_CUDA
|
||||
cuda_mem();
|
||||
#endif
|
||||
|
||||
DisplayMallinfo();
|
||||
}
|
||||
|
||||
uint64_t MemoryManager::DeviceCacheBytes() { return CacheBytes[Acc] + CacheBytes[AccHuge] + CacheBytes[AccSmall]; }
|
||||
@@ -48,6 +84,31 @@ MemoryManager::AllocationCacheEntry MemoryManager::Entries[MemoryManager::Nalloc
|
||||
int MemoryManager::Victim[MemoryManager::NallocType];
|
||||
int MemoryManager::Ncache[MemoryManager::NallocType] = { 2, 0, 8, 8, 0, 16, 8, 0, 16 };
|
||||
uint64_t MemoryManager::CacheBytes[MemoryManager::NallocType];
|
||||
uint64_t MemoryManager::DeviceAllocCalls;
|
||||
uint64_t MemoryManager::DeviceFreeCalls;
|
||||
uint64_t MemoryManager::DeviceAllocBytes;
|
||||
uint64_t MemoryManager::DeviceFreeBytes;
|
||||
uint64_t MemoryManager::DeviceCacheHits;
|
||||
|
||||
void MemoryManager::PrintAllocCounts(void)
|
||||
{
|
||||
std::cout << GridLogMemory << "MemoryManager: device allocator calls: acceleratorAllocDevice "
|
||||
<< DeviceAllocCalls <<" ("<< DeviceAllocBytes <<" bytes), acceleratorFreeDevice "
|
||||
<< DeviceFreeCalls <<" ("<< DeviceFreeBytes <<" bytes), served from ring cache "
|
||||
<< DeviceCacheHits << std::endl;
|
||||
std::cout << GridLogMemory << "MemoryManager: view traffic: HostToDevice "
|
||||
<< HostToDeviceXfer <<" transfers ("<< HostToDeviceBytes <<" bytes), DeviceToHost "
|
||||
<< DeviceToHostXfer <<" transfers ("<< DeviceToHostBytes <<" bytes), evictions "
|
||||
<< DeviceEvictions << std::endl;
|
||||
}
|
||||
void MemoryManager::Snapshot(const std::string &where)
|
||||
{
|
||||
if ( !GridLogMemory.isActive() ) return;
|
||||
std::cout << GridLogMemory << "---------------- memory snapshot: "<< where <<" ----------------"<<std::endl;
|
||||
PrintAllocCounts();
|
||||
PrintBytes();
|
||||
acceleratorMem();
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Actual allocation and deallocation utils
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@@ -57,6 +118,11 @@ void *MemoryManager::AcceleratorAllocate(size_t bytes)
|
||||
void *ptr = (void *) Lookup(bytes,Acc);
|
||||
if ( ptr == (void *) NULL ) {
|
||||
ptr = (void *) acceleratorAllocDevice(bytes);
|
||||
DeviceAllocCalls++; DeviceAllocBytes+=bytes;
|
||||
std::cout << GridLogMemory << "MemoryManager: acceleratorAllocDevice size "<< bytes
|
||||
<<" AccPtr "<< std::hex << (uint64_t)ptr << std::dec << std::endl;
|
||||
} else {
|
||||
DeviceCacheHits++;
|
||||
}
|
||||
#ifdef GRID_MM_VERBOSE
|
||||
std::cout <<"AcceleratorAllocate "<<std::endl;
|
||||
@@ -67,9 +133,13 @@ void *MemoryManager::AcceleratorAllocate(size_t bytes)
|
||||
void MemoryManager::AcceleratorFree (void *ptr,size_t bytes)
|
||||
{
|
||||
total_device-=bytes;
|
||||
void *__freeme = Insert(ptr,bytes,Acc);
|
||||
size_t freed_bytes=0;
|
||||
void *__freeme = Insert(ptr,bytes,Acc,&freed_bytes);
|
||||
if ( __freeme ) {
|
||||
acceleratorFreeDevice(__freeme);
|
||||
DeviceFreeCalls++; DeviceFreeBytes+=freed_bytes;
|
||||
std::cout << GridLogMemory << "MemoryManager: acceleratorFreeDevice size "<< freed_bytes
|
||||
<<" AccPtr "<< std::hex << (uint64_t)__freeme << std::dec << std::endl;
|
||||
}
|
||||
#ifdef GRID_MM_VERBOSE
|
||||
std::cout <<"AcceleratorFree "<<std::endl;
|
||||
@@ -236,8 +306,7 @@ void MemoryManager::InitMessage(void) {
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void *MemoryManager::Insert(void *ptr,size_t bytes,int type)
|
||||
void *MemoryManager::Insert(void *ptr,size_t bytes,int type,size_t *freed)
|
||||
{
|
||||
#ifdef ALLOCATION_CACHE
|
||||
int cache;
|
||||
@@ -245,19 +314,59 @@ void *MemoryManager::Insert(void *ptr,size_t bytes,int type)
|
||||
else if (bytes >= GRID_ALLOC_HUGE_LIMIT) cache = type + 1;
|
||||
else cache = type;
|
||||
|
||||
return Insert(ptr,bytes,Entries[cache],Ncache[cache],Victim[cache],CacheBytes[cache]);
|
||||
return Insert(ptr,bytes,Entries[cache],Ncache[cache],Victim[cache],CacheBytes[cache],freed);
|
||||
#else
|
||||
return ptr;
|
||||
#endif
|
||||
}
|
||||
void MemoryManager::DropCache(void)
|
||||
{
|
||||
// Release every block held in the allocation caches (the "recent
|
||||
// allocations" pools: Small/Large/Huge x Cpu/Acc/Shared). Blocks in these
|
||||
// pools are freed from the caller's point of view but still occupy memory;
|
||||
// after a large setup phase (SUMMA scratch, coarsening temporaries) they can
|
||||
// hold gigabytes of device memory that hipMalloc then cannot get. Each pool
|
||||
// is freed with the call that pairs with its allocator.
|
||||
for(int sz=0;sz<3;sz++) {
|
||||
int t;
|
||||
t = Acc+sz;
|
||||
for(int e=0;e<Ncache[t];e++) {
|
||||
if( Entries[t][e].valid ) {
|
||||
acceleratorFreeDevice(Entries[t][e].address);
|
||||
Entries[t][e].valid = 0; Entries[t][e].bytes = 0; Entries[t][e].address = NULL;
|
||||
}
|
||||
}
|
||||
CacheBytes[t]=0; Victim[t]=0;
|
||||
t = Shared+sz;
|
||||
for(int e=0;e<Ncache[t];e++) {
|
||||
if( Entries[t][e].valid ) {
|
||||
acceleratorFreeShared(Entries[t][e].address);
|
||||
Entries[t][e].valid = 0; Entries[t][e].bytes = 0; Entries[t][e].address = NULL;
|
||||
}
|
||||
}
|
||||
CacheBytes[t]=0; Victim[t]=0;
|
||||
t = Cpu+sz;
|
||||
for(int e=0;e<Ncache[t];e++) {
|
||||
if( Entries[t][e].valid ) {
|
||||
#ifdef GRID_UVM
|
||||
acceleratorFreeShared(Entries[t][e].address);
|
||||
#else
|
||||
acceleratorFreeCpu(Entries[t][e].address);
|
||||
#endif
|
||||
Entries[t][e].valid = 0; Entries[t][e].bytes = 0; Entries[t][e].address = NULL;
|
||||
}
|
||||
}
|
||||
CacheBytes[t]=0; Victim[t]=0;
|
||||
}
|
||||
}
|
||||
|
||||
void *MemoryManager::Insert(void *ptr,size_t bytes,AllocationCacheEntry *entries,int ncache,int &victim, uint64_t &cacheBytes)
|
||||
void *MemoryManager::Insert(void *ptr,size_t bytes,AllocationCacheEntry *entries,int ncache,int &victim, uint64_t &cacheBytes,size_t *freed)
|
||||
{
|
||||
#ifdef GRID_OMP
|
||||
assert(omp_in_parallel()==0);
|
||||
GRID_ASSERT(omp_in_parallel()==0);
|
||||
#endif
|
||||
|
||||
if (ncache == 0) return ptr;
|
||||
if (ncache == 0) { if(freed) *freed = bytes; return ptr; } // uncached: the incoming block is the one freed
|
||||
|
||||
void * ret = NULL;
|
||||
int v = -1;
|
||||
@@ -276,6 +385,7 @@ void *MemoryManager::Insert(void *ptr,size_t bytes,AllocationCacheEntry *entries
|
||||
|
||||
if ( entries[v].valid ) {
|
||||
ret = entries[v].address;
|
||||
if ( freed ) *freed = entries[v].bytes; // the DISPLACED block is what actually gets freed
|
||||
cacheBytes -= entries[v].bytes;
|
||||
entries[v].valid = 0;
|
||||
entries[v].address = NULL;
|
||||
@@ -307,7 +417,7 @@ void *MemoryManager::Lookup(size_t bytes,int type)
|
||||
void *MemoryManager::Lookup(size_t bytes,AllocationCacheEntry *entries,int ncache,uint64_t & cacheBytes)
|
||||
{
|
||||
#ifdef GRID_OMP
|
||||
assert(omp_in_parallel()==0);
|
||||
GRID_ASSERT(omp_in_parallel()==0);
|
||||
#endif
|
||||
for(int e=0;e<ncache;e++){
|
||||
if ( entries[e].valid && ( entries[e].bytes == bytes ) ) {
|
||||
|
||||
@@ -108,12 +108,12 @@ private:
|
||||
/////////////////////////////////////////////////
|
||||
// Free pool
|
||||
/////////////////////////////////////////////////
|
||||
static void *Insert(void *ptr,size_t bytes,int type) ;
|
||||
static void *Insert(void *ptr,size_t bytes,int type,size_t *freed=nullptr) ;
|
||||
static void *Lookup(size_t bytes,int type) ;
|
||||
static void *Insert(void *ptr,size_t bytes,AllocationCacheEntry *entries,int ncache,int &victim,uint64_t &cbytes) ;
|
||||
static void *Insert(void *ptr,size_t bytes,AllocationCacheEntry *entries,int ncache,int &victim,uint64_t &cbytes,size_t *freed=nullptr) ;
|
||||
static void *Lookup(size_t bytes,AllocationCacheEntry *entries,int ncache,uint64_t &cbytes) ;
|
||||
|
||||
public:
|
||||
public:
|
||||
static void PrintBytes(void);
|
||||
static void Audit(std::string s);
|
||||
static void Init(void);
|
||||
@@ -137,7 +137,19 @@ private:
|
||||
static uint64_t DeviceToHostXfer;
|
||||
static uint64_t DeviceEvictions;
|
||||
static uint64_t DeviceDestroy;
|
||||
|
||||
// Calls that actually reach the runtime, as distinct from the Evict/Clone traffic above:
|
||||
// a free only reaches acceleratorFreeDevice when displaced from the allocation ring cache,
|
||||
// and an allocate only reaches acceleratorAllocDevice on a ring miss.
|
||||
static uint64_t DeviceAllocCalls;
|
||||
static uint64_t DeviceFreeCalls;
|
||||
static uint64_t DeviceAllocBytes;
|
||||
static uint64_t DeviceFreeBytes;
|
||||
static uint64_t DeviceCacheHits;
|
||||
static void PrintAllocCounts(void);
|
||||
// Labelled snapshot: allocator counts + footprint + device free/total. Silent unless
|
||||
// --log Memory is on, so it can be left in hot code.
|
||||
static void Snapshot(const std::string &where);
|
||||
|
||||
static uint64_t DeviceCacheBytes();
|
||||
static uint64_t HostCacheBytes();
|
||||
|
||||
@@ -209,11 +221,14 @@ private:
|
||||
static void CpuViewClose(uint64_t Ptr);
|
||||
static uint64_t CpuViewOpen(uint64_t CpuPtr,size_t bytes,ViewMode mode,ViewAdvise hint);
|
||||
#endif
|
||||
static void NotifyDeletion(void * CpuPtr);
|
||||
|
||||
public:
|
||||
static void DisplayMallinfo(void);
|
||||
static void NotifyDeletion(void * CpuPtr);
|
||||
static void Print(void);
|
||||
static void PrintAll(void);
|
||||
static void EvictAll(void);
|
||||
static void DropCache(void);
|
||||
static void PrintState( void* CpuPtr);
|
||||
static int isOpen (void* CpuPtr);
|
||||
static void ViewClose(void* CpuPtr,ViewMode mode);
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#include <Grid/GridCore.h>
|
||||
#ifndef GRID_UVM
|
||||
|
||||
#warning "Using explicit device memory copies"
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
#define MAXLINE 512
|
||||
static char print_buffer [ MAXLINE ];
|
||||
|
||||
#define mprintf(...) snprintf (print_buffer,MAXLINE, __VA_ARGS__ ); std::cout << GridLogMemory << print_buffer;
|
||||
#define dprintf(...) snprintf (print_buffer,MAXLINE, __VA_ARGS__ ); std::cout << GridLogMemory << print_buffer;
|
||||
#define mprintf(...) snprintf (print_buffer,MAXLINE, __VA_ARGS__ ); std::cout << GridLogMemory << print_buffer << std::endl;
|
||||
#define dprintf(...) snprintf (print_buffer,MAXLINE, __VA_ARGS__ ); std::cout << GridLogDebug << print_buffer << std::endl;
|
||||
//#define dprintf(...)
|
||||
|
||||
//#define mprintf(...)
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// For caching copies of data on device
|
||||
@@ -51,12 +50,12 @@ int MemoryManager::EntryPresent(uint64_t CpuPtr)
|
||||
{
|
||||
if(AccViewTable.empty()) return 0;
|
||||
|
||||
auto count = AccViewTable.count(CpuPtr); assert((count==0)||(count==1));
|
||||
auto count = AccViewTable.count(CpuPtr); GRID_ASSERT((count==0)||(count==1));
|
||||
return count;
|
||||
}
|
||||
void MemoryManager::EntryCreate(uint64_t CpuPtr,size_t bytes,ViewMode mode,ViewAdvise hint)
|
||||
{
|
||||
assert(!EntryPresent(CpuPtr));
|
||||
GRID_ASSERT(!EntryPresent(CpuPtr));
|
||||
AcceleratorViewEntry AccCache;
|
||||
AccCache.CpuPtr = CpuPtr;
|
||||
AccCache.AccPtr = (uint64_t)NULL;
|
||||
@@ -70,9 +69,9 @@ void MemoryManager::EntryCreate(uint64_t CpuPtr,size_t bytes,ViewMode mode,View
|
||||
}
|
||||
MemoryManager::AccViewTableIterator MemoryManager::EntryLookup(uint64_t CpuPtr)
|
||||
{
|
||||
assert(EntryPresent(CpuPtr));
|
||||
GRID_ASSERT(EntryPresent(CpuPtr));
|
||||
auto AccCacheIterator = AccViewTable.find(CpuPtr);
|
||||
assert(AccCacheIterator!=AccViewTable.end());
|
||||
GRID_ASSERT(AccCacheIterator!=AccViewTable.end());
|
||||
return AccCacheIterator;
|
||||
}
|
||||
void MemoryManager::EntryErase(uint64_t CpuPtr)
|
||||
@@ -80,9 +79,28 @@ void MemoryManager::EntryErase(uint64_t CpuPtr)
|
||||
auto AccCache = EntryLookup(CpuPtr);
|
||||
AccViewTable.erase(CpuPtr);
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// LRU membership invariant:
|
||||
//
|
||||
// LRU_valid == 1 <=> AccPtr != NULL && accLock == 0 && cpuLock == 0
|
||||
//
|
||||
// i.e. the LRU queue contains exactly the device-resident, completely unlocked
|
||||
// entries -- the evictable set. Membership is maintained EAGERLY at the lock
|
||||
// 0<->1 edges, O(1) via the stored LRU_entry iterator:
|
||||
//
|
||||
// AcceleratorViewOpen lock 0->1 : LRUremove (gated on LRU_valid)
|
||||
// AcceleratorViewClose accLock->0: LRUinsert (AccPtr necessarily exists)
|
||||
// CpuViewOpen lock 0->1 : LRUremove (gated on LRU_valid)
|
||||
// CpuViewClose cpuLock->0: LRUinsert (iff AccPtr exists)
|
||||
// Evict/AccDiscard : LRUremove (frees the device copy)
|
||||
//
|
||||
// Consequences: victims taken from LRU.back() are evictable by construction;
|
||||
// Evict() on a locked entry is an invariant violation (asserted), and the
|
||||
// eviction loops (EvictVictims/EvictAll) cannot spin.
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
void MemoryManager::LRUinsert(AcceleratorViewEntry &AccCache)
|
||||
{
|
||||
assert(AccCache.LRU_valid==0);
|
||||
GRID_ASSERT(AccCache.LRU_valid==0);
|
||||
if (AccCache.transient) {
|
||||
LRU.push_back(AccCache.CpuPtr);
|
||||
AccCache.LRU_entry = --LRU.end();
|
||||
@@ -95,7 +113,7 @@ void MemoryManager::LRUinsert(AcceleratorViewEntry &AccCache)
|
||||
}
|
||||
void MemoryManager::LRUremove(AcceleratorViewEntry &AccCache)
|
||||
{
|
||||
assert(AccCache.LRU_valid==1);
|
||||
GRID_ASSERT(AccCache.LRU_valid==1);
|
||||
LRU.erase(AccCache.LRU_entry);
|
||||
AccCache.LRU_valid = 0;
|
||||
DeviceLRUBytes-=AccCache.bytes;
|
||||
@@ -109,19 +127,19 @@ void MemoryManager::AccDiscard(AcceleratorViewEntry &AccCache)
|
||||
// Remove from Accelerator, remove entry, without flush
|
||||
// Cannot be locked. If allocated Must be in LRU pool.
|
||||
///////////////////////////////////////////////////////////
|
||||
assert(AccCache.state!=Empty);
|
||||
GRID_ASSERT(AccCache.state!=Empty);
|
||||
|
||||
mprintf("MemoryManager: Discard(%lx) %lx\n",(uint64_t)AccCache.CpuPtr,(uint64_t)AccCache.AccPtr);
|
||||
assert(AccCache.accLock==0);
|
||||
assert(AccCache.cpuLock==0);
|
||||
assert(AccCache.CpuPtr!=(uint64_t)NULL);
|
||||
dprintf("MemoryManager: Discard(%lx) %lx",(uint64_t)AccCache.CpuPtr,(uint64_t)AccCache.AccPtr);
|
||||
GRID_ASSERT(AccCache.accLock==0);
|
||||
GRID_ASSERT(AccCache.cpuLock==0);
|
||||
GRID_ASSERT(AccCache.CpuPtr!=(uint64_t)NULL);
|
||||
if(AccCache.AccPtr) {
|
||||
AcceleratorFree((void *)AccCache.AccPtr,AccCache.bytes);
|
||||
DeviceDestroy++;
|
||||
DeviceBytes -=AccCache.bytes;
|
||||
LRUremove(AccCache);
|
||||
AccCache.AccPtr=(uint64_t) NULL;
|
||||
dprintf("MemoryManager: Free(%lx) LRU %ld Total %ld\n",(uint64_t)AccCache.AccPtr,DeviceLRUBytes,DeviceBytes);
|
||||
dprintf("MemoryManager: Free(%lx) LRU %ld Total %ld",(uint64_t)AccCache.AccPtr,DeviceLRUBytes,DeviceBytes);
|
||||
}
|
||||
uint64_t CpuPtr = AccCache.CpuPtr;
|
||||
EntryErase(CpuPtr);
|
||||
@@ -131,21 +149,21 @@ void MemoryManager::Evict(AcceleratorViewEntry &AccCache)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Make CPU consistent, remove from Accelerator, remove from LRU, LEAVE CPU only entry
|
||||
// Cannot be acclocked. If allocated must be in LRU pool.
|
||||
// Cannot be locked. If allocated must be in LRU pool.
|
||||
//
|
||||
// Nov 2022... Felix issue: Allocating two CpuPtrs, can have an entry in LRU-q with CPUlock.
|
||||
// and require to evict the AccPtr copy. Eviction was a mistake in CpuViewOpen
|
||||
// but there is a weakness where CpuLock entries are attempted for erase
|
||||
// Take these OUT LRU queue when CPU locked?
|
||||
// Cannot take out the table as cpuLock data is important.
|
||||
// (Historical: a Nov 2022 incident (two CpuPtrs; eviction called from
|
||||
// CpuViewOpen -- since excised) could present a cpuLocked entry here, and
|
||||
// silent-return guards were added. The LRU membership invariant (see
|
||||
// LRUinsert) now excludes ALL locked entries from the queue eagerly at the
|
||||
// lock edges, so a locked victim is an invariant violation: asserted.)
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
assert(AccCache.state!=Empty);
|
||||
GRID_ASSERT(AccCache.state!=Empty);
|
||||
|
||||
mprintf("MemoryManager: Evict cpu %lx acc %lx cpuLock %ld accLock %ld\n",
|
||||
mprintf("MemoryManager: Evict CpuPtr %lx AccPtr %lx cpuLock %ld accLock %ld",
|
||||
(uint64_t)AccCache.CpuPtr,(uint64_t)AccCache.AccPtr,
|
||||
(uint64_t)AccCache.cpuLock,(uint64_t)AccCache.accLock);
|
||||
if (AccCache.accLock!=0) return;
|
||||
if (AccCache.cpuLock!=0) return;
|
||||
GRID_ASSERT(AccCache.accLock==0);
|
||||
GRID_ASSERT(AccCache.cpuLock==0);
|
||||
if(AccCache.state==AccDirty) {
|
||||
Flush(AccCache);
|
||||
}
|
||||
@@ -155,7 +173,7 @@ void MemoryManager::Evict(AcceleratorViewEntry &AccCache)
|
||||
AccCache.AccPtr=(uint64_t)NULL;
|
||||
AccCache.state=CpuDirty; // CPU primary now
|
||||
DeviceBytes -=AccCache.bytes;
|
||||
dprintf("MemoryManager: Free(%lx) footprint now %ld \n",(uint64_t)AccCache.AccPtr,DeviceBytes);
|
||||
dprintf("MemoryManager: Free(AccPtr %lx) footprint now %ld ",(uint64_t)AccCache.AccPtr,DeviceBytes);
|
||||
}
|
||||
// uint64_t CpuPtr = AccCache.CpuPtr;
|
||||
DeviceEvictions++;
|
||||
@@ -163,28 +181,30 @@ void MemoryManager::Evict(AcceleratorViewEntry &AccCache)
|
||||
}
|
||||
void MemoryManager::Flush(AcceleratorViewEntry &AccCache)
|
||||
{
|
||||
assert(AccCache.state==AccDirty);
|
||||
assert(AccCache.cpuLock==0);
|
||||
assert(AccCache.accLock==0);
|
||||
assert(AccCache.AccPtr!=(uint64_t)NULL);
|
||||
assert(AccCache.CpuPtr!=(uint64_t)NULL);
|
||||
GRID_ASSERT(AccCache.state==AccDirty);
|
||||
GRID_ASSERT(AccCache.cpuLock==0);
|
||||
GRID_ASSERT(AccCache.accLock==0);
|
||||
GRID_ASSERT(AccCache.AccPtr!=(uint64_t)NULL);
|
||||
GRID_ASSERT(AccCache.CpuPtr!=(uint64_t)NULL);
|
||||
acceleratorCopyFromDevice((void *)AccCache.AccPtr,(void *)AccCache.CpuPtr,AccCache.bytes);
|
||||
mprintf("MemoryManager: Flush %lx -> %lx\n",(uint64_t)AccCache.AccPtr,(uint64_t)AccCache.CpuPtr); fflush(stdout);
|
||||
mprintf("MemoryManager: acceleratorCopyFromDevice Flush size %ld AccPtr %lx -> CpuPtr %lx",(uint64_t)AccCache.bytes,(uint64_t)AccCache.AccPtr,(uint64_t)AccCache.CpuPtr); fflush(stdout);
|
||||
DeviceToHostBytes+=AccCache.bytes;
|
||||
DeviceToHostXfer++;
|
||||
AccCache.state=Consistent;
|
||||
}
|
||||
void MemoryManager::Clone(AcceleratorViewEntry &AccCache)
|
||||
{
|
||||
assert(AccCache.state==CpuDirty);
|
||||
assert(AccCache.cpuLock==0);
|
||||
assert(AccCache.accLock==0);
|
||||
assert(AccCache.CpuPtr!=(uint64_t)NULL);
|
||||
GRID_ASSERT(AccCache.state==CpuDirty);
|
||||
GRID_ASSERT(AccCache.cpuLock==0);
|
||||
GRID_ASSERT(AccCache.accLock==0);
|
||||
GRID_ASSERT(AccCache.CpuPtr!=(uint64_t)NULL);
|
||||
if(AccCache.AccPtr==(uint64_t)NULL){
|
||||
AccCache.AccPtr=(uint64_t)AcceleratorAllocate(AccCache.bytes);
|
||||
DeviceBytes+=AccCache.bytes;
|
||||
}
|
||||
mprintf("MemoryManager: Clone %lx <- %lx\n",(uint64_t)AccCache.AccPtr,(uint64_t)AccCache.CpuPtr); fflush(stdout);
|
||||
mprintf("MemoryManager: acceleratorCopyToDevice Clone size %ld AccPtr %lx <- CpuPtr %lx",
|
||||
(uint64_t)AccCache.bytes,
|
||||
(uint64_t)AccCache.AccPtr,(uint64_t)AccCache.CpuPtr); fflush(stdout);
|
||||
acceleratorCopyToDevice((void *)AccCache.CpuPtr,(void *)AccCache.AccPtr,AccCache.bytes);
|
||||
HostToDeviceBytes+=AccCache.bytes;
|
||||
HostToDeviceXfer++;
|
||||
@@ -193,10 +213,10 @@ void MemoryManager::Clone(AcceleratorViewEntry &AccCache)
|
||||
|
||||
void MemoryManager::CpuDiscard(AcceleratorViewEntry &AccCache)
|
||||
{
|
||||
assert(AccCache.state!=Empty);
|
||||
assert(AccCache.cpuLock==0);
|
||||
assert(AccCache.accLock==0);
|
||||
assert(AccCache.CpuPtr!=(uint64_t)NULL);
|
||||
GRID_ASSERT(AccCache.state!=Empty);
|
||||
GRID_ASSERT(AccCache.cpuLock==0);
|
||||
GRID_ASSERT(AccCache.accLock==0);
|
||||
GRID_ASSERT(AccCache.CpuPtr!=(uint64_t)NULL);
|
||||
if(AccCache.AccPtr==(uint64_t)NULL){
|
||||
AccCache.AccPtr=(uint64_t)AcceleratorAllocate(AccCache.bytes);
|
||||
DeviceBytes+=AccCache.bytes;
|
||||
@@ -210,33 +230,49 @@ void MemoryManager::CpuDiscard(AcceleratorViewEntry &AccCache)
|
||||
void MemoryManager::ViewClose(void* Ptr,ViewMode mode)
|
||||
{
|
||||
if( (mode==AcceleratorRead)||(mode==AcceleratorWrite)||(mode==AcceleratorWriteDiscard) ){
|
||||
dprintf("AcceleratorViewClose %lx\n",(uint64_t)Ptr);
|
||||
dprintf("AcceleratorViewClose %lx",(uint64_t)Ptr);
|
||||
AcceleratorViewClose((uint64_t)Ptr);
|
||||
} else if( (mode==CpuRead)||(mode==CpuWrite)){
|
||||
CpuViewClose((uint64_t)Ptr);
|
||||
} else {
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
}
|
||||
void *MemoryManager::ViewOpen(void* _CpuPtr,size_t bytes,ViewMode mode,ViewAdvise hint)
|
||||
{
|
||||
uint64_t CpuPtr = (uint64_t)_CpuPtr;
|
||||
if( (mode==AcceleratorRead)||(mode==AcceleratorWrite)||(mode==AcceleratorWriteDiscard) ){
|
||||
dprintf("AcceleratorViewOpen %lx\n",(uint64_t)CpuPtr);
|
||||
dprintf("AcceleratorViewOpen %lx",(uint64_t)CpuPtr);
|
||||
return (void *) AcceleratorViewOpen(CpuPtr,bytes,mode,hint);
|
||||
} else if( (mode==CpuRead)||(mode==CpuWrite)){
|
||||
return (void *)CpuViewOpen(CpuPtr,bytes,mode,hint);
|
||||
} else {
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
void MemoryManager::EvictVictims(uint64_t bytes)
|
||||
{
|
||||
assert(bytes<DeviceMaxBytes);
|
||||
if(bytes>=DeviceMaxBytes) {
|
||||
printf("EvictVictims bytes %ld DeviceMaxBytes %ld\n",bytes,DeviceMaxBytes);
|
||||
}
|
||||
GRID_ASSERT(bytes<DeviceMaxBytes);
|
||||
while(bytes+DeviceLRUBytes > DeviceMaxBytes){
|
||||
if ( DeviceLRUBytes > 0){
|
||||
assert(LRU.size()>0);
|
||||
GRID_ASSERT(LRU.size()>0);
|
||||
uint64_t victim = LRU.back(); // From the LRU
|
||||
auto AccCacheIterator = EntryLookup(victim);
|
||||
auto & AccCache = AccCacheIterator->second;
|
||||
Evict(AccCache);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
void MemoryManager::EvictAll(void)
|
||||
{
|
||||
while(LRU.size()>0){
|
||||
if ( DeviceLRUBytes > 0){
|
||||
uint64_t victim = LRU.back(); // From the LRU
|
||||
auto AccCacheIterator = EntryLookup(victim);
|
||||
auto & AccCache = AccCacheIterator->second;
|
||||
@@ -260,19 +296,19 @@ uint64_t MemoryManager::AcceleratorViewOpen(uint64_t CpuPtr,size_t bytes,ViewMod
|
||||
if (!AccCache.AccPtr) {
|
||||
EvictVictims(bytes);
|
||||
}
|
||||
assert((mode==AcceleratorRead)||(mode==AcceleratorWrite)||(mode==AcceleratorWriteDiscard));
|
||||
GRID_ASSERT((mode==AcceleratorRead)||(mode==AcceleratorWrite)||(mode==AcceleratorWriteDiscard));
|
||||
|
||||
assert(AccCache.cpuLock==0); // Programming error
|
||||
GRID_ASSERT(AccCache.cpuLock==0); // Programming error
|
||||
|
||||
if(AccCache.state!=Empty) {
|
||||
dprintf("ViewOpen found entry %lx %lx : %ld %ld accLock %ld\n",
|
||||
dprintf("ViewOpen found entry %lx %lx : sizes %ld %ld accLock %ld",
|
||||
(uint64_t)AccCache.CpuPtr,
|
||||
(uint64_t)CpuPtr,
|
||||
(uint64_t)AccCache.bytes,
|
||||
(uint64_t)bytes,
|
||||
(uint64_t)AccCache.accLock);
|
||||
assert(AccCache.CpuPtr == CpuPtr);
|
||||
assert(AccCache.bytes ==bytes);
|
||||
GRID_ASSERT(AccCache.CpuPtr == CpuPtr);
|
||||
GRID_ASSERT(AccCache.bytes ==bytes);
|
||||
}
|
||||
/*
|
||||
* State transitions and actions
|
||||
@@ -289,7 +325,7 @@ uint64_t MemoryManager::AcceleratorViewOpen(uint64_t CpuPtr,size_t bytes,ViewMod
|
||||
* AccWrite AccDirty AccDirty - -
|
||||
*/
|
||||
if(AccCache.state==Empty) {
|
||||
assert(AccCache.LRU_valid==0);
|
||||
GRID_ASSERT(AccCache.LRU_valid==0);
|
||||
AccCache.CpuPtr = CpuPtr;
|
||||
AccCache.AccPtr = (uint64_t)NULL;
|
||||
AccCache.bytes = bytes;
|
||||
@@ -305,7 +341,7 @@ uint64_t MemoryManager::AcceleratorViewOpen(uint64_t CpuPtr,size_t bytes,ViewMod
|
||||
AccCache.state = Consistent; // Empty + AccRead => Consistent
|
||||
}
|
||||
AccCache.accLock= 1;
|
||||
dprintf("Copied Empty entry into device accLock= %d\n",AccCache.accLock);
|
||||
dprintf("Copied Empty entry into device accLock= %d",AccCache.accLock);
|
||||
} else if(AccCache.state==CpuDirty ){
|
||||
if(mode==AcceleratorWriteDiscard) {
|
||||
CpuDiscard(AccCache);
|
||||
@@ -318,30 +354,30 @@ uint64_t MemoryManager::AcceleratorViewOpen(uint64_t CpuPtr,size_t bytes,ViewMod
|
||||
AccCache.state = Consistent; // CpuDirty + AccRead => Consistent
|
||||
}
|
||||
AccCache.accLock++;
|
||||
dprintf("CpuDirty entry into device ++accLock= %d\n",AccCache.accLock);
|
||||
dprintf("CpuDirty entry into device ++accLock= %d",AccCache.accLock);
|
||||
} else if(AccCache.state==Consistent) {
|
||||
if((mode==AcceleratorWrite)||(mode==AcceleratorWriteDiscard))
|
||||
AccCache.state = AccDirty; // Consistent + AcceleratorWrite=> AccDirty
|
||||
else
|
||||
AccCache.state = Consistent; // Consistent + AccRead => Consistent
|
||||
AccCache.accLock++;
|
||||
dprintf("Consistent entry into device ++accLock= %d\n",AccCache.accLock);
|
||||
dprintf("Consistent entry into device ++accLock= %d",AccCache.accLock);
|
||||
} else if(AccCache.state==AccDirty) {
|
||||
if((mode==AcceleratorWrite)||(mode==AcceleratorWriteDiscard))
|
||||
AccCache.state = AccDirty; // AccDirty + AcceleratorWrite=> AccDirty
|
||||
else
|
||||
AccCache.state = AccDirty; // AccDirty + AccRead => AccDirty
|
||||
AccCache.accLock++;
|
||||
dprintf("AccDirty entry ++accLock= %d\n",AccCache.accLock);
|
||||
dprintf("AccDirty entry ++accLock= %d",AccCache.accLock);
|
||||
} else {
|
||||
assert(0);
|
||||
GRID_ASSERT(0);
|
||||
}
|
||||
|
||||
assert(AccCache.accLock>0);
|
||||
GRID_ASSERT(AccCache.accLock>0);
|
||||
// If view is opened on device must remove from LRU
|
||||
if(AccCache.LRU_valid==1){
|
||||
// must possibly remove from LRU as now locked on GPU
|
||||
dprintf("AccCache entry removed from LRU \n");
|
||||
dprintf("AccCache entry removed from LRU ");
|
||||
LRUremove(AccCache);
|
||||
}
|
||||
|
||||
@@ -358,16 +394,16 @@ void MemoryManager::AcceleratorViewClose(uint64_t CpuPtr)
|
||||
auto AccCacheIterator = EntryLookup(CpuPtr);
|
||||
auto & AccCache = AccCacheIterator->second;
|
||||
|
||||
assert(AccCache.cpuLock==0);
|
||||
assert(AccCache.accLock>0);
|
||||
GRID_ASSERT(AccCache.cpuLock==0);
|
||||
GRID_ASSERT(AccCache.accLock>0);
|
||||
|
||||
AccCache.accLock--;
|
||||
// Move to LRU queue if not locked and close on device
|
||||
if(AccCache.accLock==0) {
|
||||
dprintf("AccleratorViewClose %lx AccLock decremented to %ld move to LRU queue\n",(uint64_t)CpuPtr,(uint64_t)AccCache.accLock);
|
||||
dprintf("AccleratorViewClose %lx AccLock decremented to %ld move to LRU queue",(uint64_t)CpuPtr,(uint64_t)AccCache.accLock);
|
||||
LRUinsert(AccCache);
|
||||
} else {
|
||||
dprintf("AccleratorViewClose %lx AccLock decremented to %ld\n",(uint64_t)CpuPtr,(uint64_t)AccCache.accLock);
|
||||
dprintf("AccleratorViewClose %lx AccLock decremented to %ld",(uint64_t)CpuPtr,(uint64_t)AccCache.accLock);
|
||||
}
|
||||
}
|
||||
void MemoryManager::CpuViewClose(uint64_t CpuPtr)
|
||||
@@ -375,10 +411,17 @@ void MemoryManager::CpuViewClose(uint64_t CpuPtr)
|
||||
auto AccCacheIterator = EntryLookup(CpuPtr);
|
||||
auto & AccCache = AccCacheIterator->second;
|
||||
|
||||
assert(AccCache.cpuLock>0);
|
||||
assert(AccCache.accLock==0);
|
||||
GRID_ASSERT(AccCache.cpuLock>0);
|
||||
GRID_ASSERT(AccCache.accLock==0);
|
||||
|
||||
AccCache.cpuLock--;
|
||||
// Return to LRU queue when fully unlocked -- mirrors AcceleratorViewClose.
|
||||
// Asymmetry vs the Acc side: a device copy need not exist for a host view;
|
||||
// only device-resident entries belong in the (evictable) LRU queue.
|
||||
if( (AccCache.cpuLock==0) && (AccCache.AccPtr!=(uint64_t)NULL) ) {
|
||||
dprintf("CpuViewClose %lx cpuLock decremented to zero, move to LRU queue",(uint64_t)CpuPtr);
|
||||
LRUinsert(AccCache);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Action State StateNext Flush Clone
|
||||
@@ -409,12 +452,12 @@ uint64_t MemoryManager::CpuViewOpen(uint64_t CpuPtr,size_t bytes,ViewMode mode,V
|
||||
// EvictVictims(bytes);
|
||||
// }
|
||||
|
||||
assert((mode==CpuRead)||(mode==CpuWrite));
|
||||
assert(AccCache.accLock==0); // Programming error
|
||||
GRID_ASSERT((mode==CpuRead)||(mode==CpuWrite));
|
||||
GRID_ASSERT(AccCache.accLock==0); // Programming error
|
||||
|
||||
if(AccCache.state!=Empty) {
|
||||
assert(AccCache.CpuPtr == CpuPtr);
|
||||
assert(AccCache.bytes==bytes);
|
||||
GRID_ASSERT(AccCache.CpuPtr == CpuPtr);
|
||||
GRID_ASSERT(AccCache.bytes==bytes);
|
||||
}
|
||||
|
||||
if(AccCache.state==Empty) {
|
||||
@@ -429,20 +472,28 @@ uint64_t MemoryManager::CpuViewOpen(uint64_t CpuPtr,size_t bytes,ViewMode mode,V
|
||||
AccCache.state = CpuDirty; // CpuDirty +CpuRead/CpuWrite => CpuDirty
|
||||
AccCache.cpuLock++;
|
||||
} else if(AccCache.state==Consistent) {
|
||||
assert(AccCache.AccPtr != (uint64_t)NULL);
|
||||
GRID_ASSERT(AccCache.AccPtr != (uint64_t)NULL);
|
||||
if(mode==CpuWrite)
|
||||
AccCache.state = CpuDirty; // Consistent +CpuWrite => CpuDirty
|
||||
else
|
||||
AccCache.state = Consistent; // Consistent +CpuRead => Consistent
|
||||
AccCache.cpuLock++;
|
||||
} else if(AccCache.state==AccDirty) {
|
||||
assert(AccCache.AccPtr != (uint64_t)NULL);
|
||||
GRID_ASSERT(AccCache.AccPtr != (uint64_t)NULL);
|
||||
Flush(AccCache);
|
||||
if(mode==CpuWrite) AccCache.state = CpuDirty; // AccDirty +CpuWrite => CpuDirty, Flush
|
||||
else AccCache.state = Consistent; // AccDirty +CpuRead => Consistent, Flush
|
||||
AccCache.cpuLock++;
|
||||
} else {
|
||||
assert(0); // should be unreachable
|
||||
GRID_ASSERT(0); // should be unreachable
|
||||
}
|
||||
|
||||
GRID_ASSERT(AccCache.cpuLock>0);
|
||||
// If view is opened on host must remove from LRU -- mirrors AcceleratorViewOpen.
|
||||
// LRU_valid==1 here implies this is the 0->1 lock edge of a device-resident entry.
|
||||
if(AccCache.LRU_valid==1){
|
||||
dprintf("CpuViewOpen: entry removed from LRU ");
|
||||
LRUremove(AccCache);
|
||||
}
|
||||
|
||||
AccCache.transient= transient? EvictNext : 0;
|
||||
@@ -474,6 +525,7 @@ void MemoryManager::Print(void)
|
||||
std::cout << GridLogMessage << DeviceEvictions << " Evictions from device " << std::endl;
|
||||
std::cout << GridLogMessage << DeviceDestroy << " Destroyed vectors on device " << std::endl;
|
||||
std::cout << GridLogMessage << AccViewTable.size()<< " vectors " << LRU.size()<<" evictable"<< std::endl;
|
||||
acceleratorMem();
|
||||
std::cout << GridLogMessage << "--------------------------------------------" << std::endl;
|
||||
}
|
||||
void MemoryManager::PrintAll(void)
|
||||
@@ -523,12 +575,12 @@ void MemoryManager::Audit(std::string s)
|
||||
std::cout << " Memory Manager::Audit() from "<<s<<std::endl;
|
||||
for(auto it=LRU.begin();it!=LRU.end();it++){
|
||||
uint64_t cpuPtr = *it;
|
||||
assert(EntryPresent(cpuPtr));
|
||||
GRID_ASSERT(EntryPresent(cpuPtr));
|
||||
auto AccCacheIterator = EntryLookup(cpuPtr);
|
||||
auto & AccCache = AccCacheIterator->second;
|
||||
LruBytes2+=AccCache.bytes;
|
||||
assert(AccCache.LRU_valid==1);
|
||||
assert(AccCache.LRU_entry==it);
|
||||
GRID_ASSERT(AccCache.LRU_valid==1);
|
||||
GRID_ASSERT(AccCache.LRU_entry==it);
|
||||
}
|
||||
std::cout << " Memory Manager::Audit() LRU queue matches table entries "<<std::endl;
|
||||
|
||||
@@ -547,7 +599,7 @@ void MemoryManager::Audit(std::string s)
|
||||
if( AccCache.LRU_valid ) LruCnt++;
|
||||
|
||||
if ( AccCache.cpuLock || AccCache.accLock ) {
|
||||
assert(AccCache.LRU_valid==0);
|
||||
GRID_ASSERT(AccCache.LRU_valid==0);
|
||||
|
||||
std::cout << GridLogError << s<< "\n\t 0x"<<std::hex<<AccCache.CpuPtr<<std::dec
|
||||
<< "\t0x"<<std::hex<<AccCache.AccPtr<<std::dec<<"\t" <<str
|
||||
@@ -556,16 +608,16 @@ void MemoryManager::Audit(std::string s)
|
||||
<< "\t LRUvalid " << AccCache.LRU_valid<<std::endl;
|
||||
}
|
||||
|
||||
assert( AccCache.cpuLock== 0 ) ;
|
||||
assert( AccCache.accLock== 0 ) ;
|
||||
GRID_ASSERT( AccCache.cpuLock== 0 ) ;
|
||||
GRID_ASSERT( AccCache.accLock== 0 ) ;
|
||||
}
|
||||
std::cout << " Memory Manager::Audit() no locked table entries "<<std::endl;
|
||||
assert(LruBytes1==LruBytes2);
|
||||
assert(LruBytes1==DeviceLRUBytes);
|
||||
GRID_ASSERT(LruBytes1==LruBytes2);
|
||||
GRID_ASSERT(LruBytes1==DeviceLRUBytes);
|
||||
std::cout << " Memory Manager::Audit() evictable bytes matches sum over table "<<std::endl;
|
||||
assert(AccBytes==DeviceBytes);
|
||||
GRID_ASSERT(AccBytes==DeviceBytes);
|
||||
std::cout << " Memory Manager::Audit() device bytes matches sum over table "<<std::endl;
|
||||
assert(LruCnt == LRU.size());
|
||||
GRID_ASSERT(LruCnt == LRU.size());
|
||||
std::cout << " Memory Manager::Audit() LRU entry count matches "<<std::endl;
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ void MemoryManager::PrintState(void* CpuPtr)
|
||||
std::cout << GridLogMessage << "Host<->Device memory movement not currently managed by Grid." << std::endl;
|
||||
};
|
||||
void MemoryManager::Print(void){};
|
||||
void MemoryManager::EvictAll(void){}; // nothing to evict in unified memory
|
||||
void MemoryManager::PrintAll(void){};
|
||||
void MemoryManager::NotifyDeletion(void *ptr){};
|
||||
|
||||
|
||||
@@ -10,16 +10,16 @@ void check_huge_pages(void *Buf,uint64_t BYTES)
|
||||
{
|
||||
#ifdef __linux__
|
||||
int fd = open("/proc/self/pagemap", O_RDONLY);
|
||||
assert(fd >= 0);
|
||||
GRID_ASSERT(fd >= 0);
|
||||
const int page_size = 4096;
|
||||
uint64_t virt_pfn = (uint64_t)Buf / page_size;
|
||||
off_t offset = sizeof(uint64_t) * virt_pfn;
|
||||
uint64_t npages = (BYTES + page_size-1) / page_size;
|
||||
uint64_t pagedata[npages];
|
||||
std::vector<uint64_t> pagedata(npages);
|
||||
uint64_t ret = lseek(fd, offset, SEEK_SET);
|
||||
assert(ret == offset);
|
||||
ret = ::read(fd, pagedata, sizeof(uint64_t)*npages);
|
||||
assert(ret == sizeof(uint64_t) * npages);
|
||||
GRID_ASSERT(ret == offset);
|
||||
ret = ::read(fd, &pagedata[0], sizeof(uint64_t)*npages);
|
||||
GRID_ASSERT(ret == sizeof(uint64_t) * npages);
|
||||
int nhugepages = npages / 512;
|
||||
int n4ktotal, nnothuge;
|
||||
n4ktotal = 0;
|
||||
|
||||
@@ -70,8 +70,8 @@ public:
|
||||
Coordinate _istride; // Inner stride i.e. within simd lane
|
||||
int _osites; // _isites*_osites = product(dimensions).
|
||||
int _isites;
|
||||
int _fsites; // _isites*_osites = product(dimensions).
|
||||
int _gsites;
|
||||
int64_t _fsites; // _isites*_osites = product(dimensions).
|
||||
int64_t _gsites;
|
||||
Coordinate _slice_block;// subslice information
|
||||
Coordinate _slice_stride;
|
||||
Coordinate _slice_nblock;
|
||||
@@ -82,6 +82,7 @@ public:
|
||||
bool _isCheckerBoarded;
|
||||
int LocallyPeriodic;
|
||||
Coordinate _checker_dim_mask;
|
||||
int _checker_dim;
|
||||
|
||||
public:
|
||||
|
||||
@@ -89,7 +90,7 @@ public:
|
||||
// Checkerboarding interface is virtual and overridden by
|
||||
// GridCartesian / GridRedBlackCartesian
|
||||
////////////////////////////////////////////////////////////////
|
||||
virtual int CheckerBoarded(int dim)=0;
|
||||
virtual int CheckerBoarded(int dim) =0;
|
||||
virtual int CheckerBoard(const Coordinate &site)=0;
|
||||
virtual int CheckerBoardDestination(int source_cb,int shift,int dim)=0;
|
||||
virtual int CheckerBoardShift(int source_cb,int dim,int shift,int osite)=0;
|
||||
@@ -164,7 +165,7 @@ public:
|
||||
//
|
||||
if ( _simd_layout[dimension] > 2 ) {
|
||||
for(int d=0;d<_ndimension;d++){
|
||||
if ( d != dimension ) assert ( (_simd_layout[d]==1) );
|
||||
if ( d != dimension ) GRID_ASSERT ( (_simd_layout[d]==1) );
|
||||
}
|
||||
permute_type = RotateBit; // How to specify distance; this is not just direction.
|
||||
return permute_type;
|
||||
@@ -183,10 +184,10 @@ public:
|
||||
inline int Nsimd(void) const { return _isites; };// Synonymous with iSites
|
||||
inline int oSites(void) const { return _osites; };
|
||||
inline int lSites(void) const { return _isites*_osites; };
|
||||
inline int gSites(void) const { return _isites*_osites*_Nprocessors; };
|
||||
inline int64_t gSites(void) const { return (int64_t)_isites*(int64_t)_osites*(int64_t)_Nprocessors; };
|
||||
inline int Nd (void) const { return _ndimension;};
|
||||
|
||||
inline const Coordinate LocalStarts(void) { return _lstart; };
|
||||
inline const Coordinate &LocalStarts(void) { return _lstart; };
|
||||
inline const Coordinate &FullDimensions(void) { return _fdimensions;};
|
||||
inline const Coordinate &GlobalDimensions(void) { return _gdimensions;};
|
||||
inline const Coordinate &LocalDimensions(void) { return _ldimensions;};
|
||||
@@ -214,15 +215,15 @@ public:
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Global addressing
|
||||
////////////////////////////////////////////////////////////////
|
||||
void GlobalIndexToGlobalCoor(int gidx,Coordinate &gcoor){
|
||||
assert(gidx< gSites());
|
||||
void GlobalIndexToGlobalCoor(int64_t gidx,Coordinate &gcoor){
|
||||
GRID_ASSERT(gidx< gSites());
|
||||
Lexicographic::CoorFromIndex(gcoor,gidx,_gdimensions);
|
||||
}
|
||||
void LocalIndexToLocalCoor(int lidx,Coordinate &lcoor){
|
||||
assert(lidx<lSites());
|
||||
GRID_ASSERT(lidx<lSites());
|
||||
Lexicographic::CoorFromIndex(lcoor,lidx,_ldimensions);
|
||||
}
|
||||
void GlobalCoorToGlobalIndex(const Coordinate & gcoor,int & gidx){
|
||||
void GlobalCoorToGlobalIndex(const Coordinate & gcoor,int64_t & gidx){
|
||||
gidx=0;
|
||||
int mult=1;
|
||||
for(int mu=0;mu<_ndimension;mu++) {
|
||||
|
||||
@@ -38,7 +38,7 @@ class GridCartesian: public GridBase {
|
||||
|
||||
public:
|
||||
int dummy;
|
||||
Coordinate _checker_dim_mask;
|
||||
// Coordinate _checker_dim_mask;
|
||||
virtual int CheckerBoardFromOindexTable (int Oindex) {
|
||||
return 0;
|
||||
}
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
virtual int CheckerBoarded(int dim){
|
||||
virtual int CheckerBoarded(int dim) {
|
||||
return 0;
|
||||
}
|
||||
virtual int CheckerBoard(const Coordinate &site){
|
||||
@@ -106,6 +106,7 @@ public:
|
||||
_rdimensions.resize(_ndimension);
|
||||
_simd_layout.resize(_ndimension);
|
||||
_checker_dim_mask.resize(_ndimension);;
|
||||
_checker_dim = -1;
|
||||
_lstart.resize(_ndimension);
|
||||
_lend.resize(_ndimension);
|
||||
|
||||
@@ -127,10 +128,10 @@ public:
|
||||
// Use a reduced simd grid
|
||||
_ldimensions[d] = _gdimensions[d] / _processors[d]; //local dimensions
|
||||
//std::cout << _ldimensions[d] << " " << _gdimensions[d] << " " << _processors[d] << std::endl;
|
||||
assert(_ldimensions[d] * _processors[d] == _gdimensions[d]);
|
||||
GRID_ASSERT(_ldimensions[d] * _processors[d] == _gdimensions[d]);
|
||||
|
||||
_rdimensions[d] = _ldimensions[d] / _simd_layout[d]; //overdecomposition
|
||||
assert(_rdimensions[d] * _simd_layout[d] == _ldimensions[d]);
|
||||
GRID_ASSERT(_rdimensions[d] * _simd_layout[d] == _ldimensions[d]);
|
||||
|
||||
_lstart[d] = _processor_coor[d] * _ldimensions[d];
|
||||
_lend[d] = _processor_coor[d] * _ldimensions[d] + _ldimensions[d] - 1;
|
||||
|
||||
@@ -57,16 +57,17 @@ class GridRedBlackCartesian : public GridBase
|
||||
{
|
||||
public:
|
||||
// Coordinate _checker_dim_mask;
|
||||
int _checker_dim;
|
||||
// int _checker_dim;
|
||||
std::vector<int> _checker_board;
|
||||
|
||||
virtual int isCheckerBoarded(void) const { return 1; };
|
||||
virtual int CheckerBoarded(int dim){
|
||||
if( dim==_checker_dim) return 1;
|
||||
else return 0;
|
||||
}
|
||||
virtual int CheckerBoard(const Coordinate &site){
|
||||
int linear=0;
|
||||
assert(site.size()==_ndimension);
|
||||
GRID_ASSERT(site.size()==_ndimension);
|
||||
for(int d=0;d<_ndimension;d++){
|
||||
if(_checker_dim_mask[d])
|
||||
linear=linear+site[d];
|
||||
@@ -147,7 +148,7 @@ public:
|
||||
{
|
||||
Init(base->_fdimensions,base->_simd_layout,base->_processors,checker_dim_mask,checker_dim) ;
|
||||
}
|
||||
|
||||
|
||||
virtual ~GridRedBlackCartesian() = default;
|
||||
|
||||
void Init(const Coordinate &dimensions,
|
||||
@@ -159,11 +160,11 @@ public:
|
||||
|
||||
_isCheckerBoarded = true;
|
||||
_checker_dim = checker_dim;
|
||||
assert(checker_dim_mask[checker_dim] == 1);
|
||||
GRID_ASSERT(checker_dim_mask[checker_dim] == 1);
|
||||
_ndimension = dimensions.size();
|
||||
assert(checker_dim_mask.size() == _ndimension);
|
||||
assert(processor_grid.size() == _ndimension);
|
||||
assert(simd_layout.size() == _ndimension);
|
||||
GRID_ASSERT(checker_dim_mask.size() == _ndimension);
|
||||
GRID_ASSERT(processor_grid.size() == _ndimension);
|
||||
GRID_ASSERT(simd_layout.size() == _ndimension);
|
||||
|
||||
_fdimensions.resize(_ndimension);
|
||||
_gdimensions.resize(_ndimension);
|
||||
@@ -189,20 +190,20 @@ public:
|
||||
|
||||
if (d == _checker_dim)
|
||||
{
|
||||
assert((_gdimensions[d] & 0x1) == 0);
|
||||
GRID_ASSERT((_gdimensions[d] & 0x1) == 0);
|
||||
_gdimensions[d] = _gdimensions[d] / 2; // Remove a checkerboard
|
||||
_gsites /= 2;
|
||||
}
|
||||
_ldimensions[d] = _gdimensions[d] / _processors[d];
|
||||
assert(_ldimensions[d] * _processors[d] == _gdimensions[d]);
|
||||
GRID_ASSERT(_ldimensions[d] * _processors[d] == _gdimensions[d]);
|
||||
_lstart[d] = _processor_coor[d] * _ldimensions[d];
|
||||
_lend[d] = _processor_coor[d] * _ldimensions[d] + _ldimensions[d] - 1;
|
||||
|
||||
// Use a reduced simd grid
|
||||
_simd_layout[d] = simd_layout[d];
|
||||
_rdimensions[d] = _ldimensions[d] / _simd_layout[d]; // this is not checking if this is integer
|
||||
assert(_rdimensions[d] * _simd_layout[d] == _ldimensions[d]);
|
||||
assert(_rdimensions[d] > 0);
|
||||
GRID_ASSERT(_rdimensions[d] * _simd_layout[d] == _ldimensions[d]);
|
||||
GRID_ASSERT(_rdimensions[d] > 0);
|
||||
|
||||
// all elements of a simd vector must have same checkerboard.
|
||||
// If Ls vectorised, this must still be the case; e.g. dwf rb5d
|
||||
|
||||
@@ -34,6 +34,10 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
bool Stencil_force_mpi = true;
|
||||
#ifdef GRID_FJMPI
|
||||
bool Stencil_persistent = false;
|
||||
#endif
|
||||
bool Stencil_force_barrier = false;
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Info that is setup once and indept of cartesian layout
|
||||
@@ -57,18 +61,29 @@ int CartesianCommunicator::ProcessorCount(void) { return
|
||||
// very VERY rarely (Log, serial RNG) we need world without a grid
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef USE_GRID_REDUCTION
|
||||
void CartesianCommunicator::GlobalSum(ComplexF &c)
|
||||
{
|
||||
GlobalSumP2P(c);
|
||||
}
|
||||
void CartesianCommunicator::GlobalSum(ComplexD &c)
|
||||
{
|
||||
GlobalSumP2P(c);
|
||||
}
|
||||
#else
|
||||
void CartesianCommunicator::GlobalSum(ComplexF &c)
|
||||
{
|
||||
GlobalSumVector((float *)&c,2);
|
||||
}
|
||||
void CartesianCommunicator::GlobalSumVector(ComplexF *c,int N)
|
||||
{
|
||||
GlobalSumVector((float *)c,2*N);
|
||||
}
|
||||
void CartesianCommunicator::GlobalSum(ComplexD &c)
|
||||
{
|
||||
GlobalSumVector((double *)&c,2);
|
||||
}
|
||||
#endif
|
||||
void CartesianCommunicator::GlobalSumVector(ComplexF *c,int N)
|
||||
{
|
||||
GlobalSumVector((float *)c,2*N);
|
||||
}
|
||||
void CartesianCommunicator::GlobalSumVector(ComplexD *c,int N)
|
||||
{
|
||||
GlobalSumVector((double *)c,2*N);
|
||||
|
||||
@@ -33,9 +33,16 @@ Author: Peter Boyle <paboyle@ph.ed.ac.uk>
|
||||
///////////////////////////////////
|
||||
#include <Grid/communicator/SharedMemory.h>
|
||||
|
||||
#define NVLINK_GET
|
||||
|
||||
NAMESPACE_BEGIN(Grid);
|
||||
|
||||
extern bool Stencil_force_mpi ;
|
||||
#ifdef GRID_FJMPI
|
||||
// Fujitsu MPI persistent requests for the halo exchange with other nodes (set by --comms-overlap)
|
||||
extern bool Stencil_persistent ;
|
||||
#endif
|
||||
extern bool Stencil_force_barrier ;
|
||||
|
||||
class CartesianCommunicator : public SharedMemory {
|
||||
|
||||
@@ -106,7 +113,7 @@ public:
|
||||
// very VERY rarely (Log, serial RNG) we need world without a grid
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
static int RankWorld(void) ;
|
||||
static void BroadcastWorld(int root,void* data, int bytes);
|
||||
static void BroadcastWorld(int root,void* data, uint64_t bytes);
|
||||
static void BarrierWorld(void);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
@@ -127,7 +134,37 @@ public:
|
||||
void GlobalSumVector(ComplexD *c,int N);
|
||||
void GlobalXOR(uint32_t &);
|
||||
void GlobalXOR(uint64_t &);
|
||||
|
||||
|
||||
template<class obj> void GlobalSumP2P(obj &o)
|
||||
{
|
||||
GRID_TRACE("GlobalSumP2P");
|
||||
std::vector<obj> column;
|
||||
obj accum = o;
|
||||
int source,dest;
|
||||
for(int d=0;d<_ndimension;d++){
|
||||
column.resize(_processors[d]);
|
||||
column[0] = accum;
|
||||
std::vector<MpiCommsRequest_t> list;
|
||||
for(int p=1;p<_processors[d];p++){
|
||||
ShiftedRanks(d,p,source,dest);
|
||||
SendToRecvFromBegin(list,
|
||||
&column[0],
|
||||
dest,
|
||||
&column[p],
|
||||
source,
|
||||
sizeof(obj),d*100+p);
|
||||
|
||||
}
|
||||
if (!list.empty()) // avoid triggering GRID_ASSERT in comms == none
|
||||
CommsComplete(list);
|
||||
for(int p=1;p<_processors[d];p++){
|
||||
accum = accum + column[p];
|
||||
}
|
||||
}
|
||||
Broadcast(0,accum);
|
||||
o=accum;
|
||||
}
|
||||
|
||||
template<class obj> void GlobalSum(obj &o){
|
||||
typedef typename obj::scalar_type scalar_type;
|
||||
int words = sizeof(obj)/sizeof(scalar_type);
|
||||
@@ -138,24 +175,49 @@ public:
|
||||
////////////////////////////////////////////////////////////
|
||||
// Face exchange, buffer swap in translational invariant way
|
||||
////////////////////////////////////////////////////////////
|
||||
void CommsComplete(std::vector<MpiCommsRequest_t> &list);
|
||||
void SendToRecvFromBegin(std::vector<MpiCommsRequest_t> &list,
|
||||
void *xmit,
|
||||
int dest,
|
||||
void *recv,
|
||||
int from,
|
||||
uint64_t bytes,int dir);
|
||||
|
||||
void SendToRecvFrom(void *xmit,
|
||||
int xmit_to_rank,
|
||||
void *recv,
|
||||
int recv_from_rank,
|
||||
int bytes);
|
||||
uint64_t bytes);
|
||||
|
||||
int IsOffNode(int rank);
|
||||
double StencilSendToRecvFrom(void *xmit,
|
||||
int xmit_to_rank,int do_xmit,
|
||||
void *recv,
|
||||
int recv_from_rank,int do_recv,
|
||||
int bytes,int dir);
|
||||
uint64_t bytes,int dir);
|
||||
|
||||
double StencilSendToRecvFromPrepare(std::vector<CommsRequest_t> &list,
|
||||
void *xmit,
|
||||
int xmit_to_rank,int do_xmit,
|
||||
void *recv,
|
||||
int recv_from_rank,int do_recv,
|
||||
uint64_t xbytes,uint64_t rbytes,int dir);
|
||||
|
||||
// Could do a PollHtoD and have a CommsMerge dependence
|
||||
void StencilSendToRecvFromPollDtoH (std::vector<CommsRequest_t> &list);
|
||||
#ifdef A64FX
|
||||
// Make progress on outstanding requests without blocking (MPI only progresses
|
||||
// inside MPI calls on Fugaku); safe to call repeatedly before Complete.
|
||||
void StencilSendToRecvFromProgress(std::vector<CommsRequest_t> &list);
|
||||
#endif
|
||||
void StencilSendToRecvFromPollIRecv(std::vector<CommsRequest_t> &list);
|
||||
|
||||
double StencilSendToRecvFromBegin(std::vector<CommsRequest_t> &list,
|
||||
void *xmit,
|
||||
void *xmit,void *xmit_comp,
|
||||
int xmit_to_rank,int do_xmit,
|
||||
void *recv,
|
||||
void *recv,void *recv_comp,
|
||||
int recv_from_rank,int do_recv,
|
||||
int xbytes,int rbytes,int dir);
|
||||
uint64_t xbytes,uint64_t rbytes,int dir);
|
||||
|
||||
|
||||
void StencilSendToRecvFromComplete(std::vector<CommsRequest_t> &waitall,int i);
|
||||
@@ -169,24 +231,51 @@ public:
|
||||
////////////////////////////////////////////////////////////
|
||||
// Broadcast a buffer and composite larger
|
||||
////////////////////////////////////////////////////////////
|
||||
void Broadcast(int root,void* data, int bytes);
|
||||
void Broadcast(int root,void* data, uint64_t bytes);
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// All2All down one dimension
|
||||
////////////////////////////////////////////////////////////
|
||||
template<class T> void AllToAll(int dim,std::vector<T> &in, std::vector<T> &out){
|
||||
assert(dim>=0);
|
||||
assert(dim<_ndimension);
|
||||
assert(in.size()==out.size());
|
||||
GRID_ASSERT(dim>=0);
|
||||
GRID_ASSERT(dim<_ndimension);
|
||||
GRID_ASSERT(in.size()==out.size());
|
||||
int numnode = _processors[dim];
|
||||
uint64_t bytes=sizeof(T);
|
||||
uint64_t words=in.size()/numnode;
|
||||
assert(numnode * words == in.size());
|
||||
assert(words < (1ULL<<31));
|
||||
GRID_ASSERT(numnode * words == in.size());
|
||||
GRID_ASSERT(words < (1ULL<<31));
|
||||
AllToAll(dim,(void *)&in[0],(void *)&out[0],words,bytes);
|
||||
}
|
||||
void AllToAll(int dim ,void *in,void *out,uint64_t words,uint64_t bytes);
|
||||
void AllToAll(void *in,void *out,uint64_t words ,uint64_t bytes);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Variable count all to all. Counts and displacements are in units of
|
||||
// "bytes" sized words and are indexed by rank within this communicator.
|
||||
// For exchanges that are a permutation but do not divide evenly between
|
||||
// ranks; AllToAll above is the uniform count special case.
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
void AllToAllV(void *in ,const std::vector<int> &sendcounts,const std::vector<int> &senddispls,
|
||||
void *out,const std::vector<int> &recvcounts,const std::vector<int> &recvdispls,
|
||||
uint64_t bytes);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Gather to all. Every rank contributes sendcount words from "in"; every
|
||||
// rank receives the concatenation of all contributions in rank order.
|
||||
// Counts and displacements are in units of "bytes" sized words and are
|
||||
// indexed by rank within this communicator; recvcounts[ThisRank()] must
|
||||
// equal sendcount. AllGather below is the uniform count special case.
|
||||
//
|
||||
// This is the primitive form of the zero-fill + GlobalSumVector idiom used
|
||||
// wherever each element of the result has exactly one contributing rank.
|
||||
// That idiom moves the payload twice and reduces over zeros; this moves it
|
||||
// once and performs no arithmetic.
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
void AllGatherV(void *in ,int sendcount,
|
||||
void *out,const std::vector<int> &recvcounts,const std::vector<int> &recvdispls,
|
||||
uint64_t bytes);
|
||||
void AllGather (void *in ,void *out,uint64_t words,uint64_t bytes);
|
||||
|
||||
template<class obj> void Broadcast(int root,obj &data)
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Loaded 100 of 708 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user