Repository navigation
Fixes issue 225 by explicit symmetrization of density matrices #226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,7 @@ | |
| #include "host/local_host_work_driver.hpp" | ||
| #include "host/blas.hpp" | ||
| #include <stdexcept> | ||
| #include <vector> | ||
|
|
||
| namespace GauXC::detail { | ||
|
|
||
|
|
@@ -57,6 +58,43 @@ void ReferenceReplicatedXCHostIntegrator<ValueType>:: | |
| GAUXC_GENERIC_EXCEPTION("Invalid LDFXCZ"); | ||
|
|
||
|
|
||
| // Symmetrize the TRIAL densities. | ||
| // | ||
| // The XC energy is a functional of n = Tr[P B B], which sees only the | ||
| // symmetric part of P: the derivative of Exc along an antisymmetric | ||
| // direction is identically zero. The density channel gets that for | ||
| // free -- Tr[P B B] kills an antisymmetric P by itself -- but the | ||
| // GRADIENT channel does not. eval_uvvar_* builds it as | ||
| // d_x n = 2 X . d_x B, X = fac * P * B, | ||
| // which equals Tr[(P + P^T) B d_x B] only when P is symmetric. That | ||
| // holds for the reference density, so exc_vxc and exc_grad are | ||
| // unaffected; it does NOT hold for a trial density, which a caller has | ||
| // no reason to hand over symmetric -- a TDDFT B-matrix or stability | ||
| // block is contracted against the ANTISYMMETRIC combination on | ||
| // purpose. The result was an XC kernel that answered nonzero to a | ||
| // direction along which Exc does not vary, breaking the Hermiticity | ||
| // of the Kohn-Sham Hessian for every GGA and meta-GGA while leaving | ||
| // LDA (which never touches the gradient channel) looking correct. | ||
| // See https://github.com/wavefunction91/GauXC/issues/225. | ||
| // | ||
| // Symmetrizing here rather than in the uvvar kernels keeps the fix in | ||
| // one place for every backend and every rung, and costs one nbf^2 | ||
| // copy against a grid loop. | ||
| std::vector<value_type> tPs_sym, tPz_sym; | ||
| auto symmetrize = [nbf]( const value_type* A, int64_t lda, | ||
| std::vector<value_type>& out ) -> const value_type* { | ||
| if( !A ) return nullptr; | ||
| out.resize( nbf*nbf ); | ||
| for( int64_t j = 0; j < nbf; ++j ) | ||
| for( int64_t i = 0; i < nbf; ++i ) | ||
| out[i + j*nbf] = 0.5 * ( A[i + j*lda] + A[j + i*lda] ); | ||
| return out.data(); | ||
| }; | ||
| const value_type* tPs_use = symmetrize( tPs, ldtps, tPs_sym ); | ||
| const value_type* tPz_use = symmetrize( tPz, ldtpz, tPz_sym ); | ||
|
Comment on lines
+93
to
+94
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed in 9e17cd2. Both |
||
| const int64_t ldtps_use = tPs_use ? nbf : ldtps; | ||
| const int64_t ldtpz_use = tPz_use ? nbf : ldtpz; | ||
|
|
||
| // Get Tasks | ||
| auto& tasks = this->load_balancer_->get_tasks(); | ||
|
|
||
|
|
@@ -66,7 +104,7 @@ void ReferenceReplicatedXCHostIntegrator<ValueType>:: | |
| // Compute Local contributions to FXC contraction | ||
| this->timer_.time_op("XCIntegrator.LocalWork", [&](){ | ||
| fxc_contraction_local_work_( basis, Ps, ldps, Pz, ldpz, | ||
| tPs, ldtps, tPz, ldtpz, | ||
| tPs_use, ldtps_use, tPz_use, ldtpz_use, | ||
| FXCs, ldfxcs, FXCz, ldfxcz, | ||
| &N_EL, ks_settings, | ||
| tasks.begin(), tasks.end() ); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed in 9e17cd2.
Worth noting why the existing test was blind to this: every stored trial density in the reference data is exactly symmetric (
max|tP - tPᵀ| = 0.0), so antisymmetrizing the stored matrices would also have given a vacuous test.Instead,
test_fxc_contractioin— the shared helper, so it runs for Host and every Device section — now builds a non-symmetricMwhose symmetric part is bitwise the stored trial density (strict upper triangle doubled, lower zeroed), and checks, for RKS and both UKS channels:F[M]reproduces the storedFXCreference, i.e.F[M] == F[(M+Mᵀ)/2]against independent data;F[(M−Mᵀ)/2]vanishes.Verified on a host build that it discriminates:
LDA sections pass either way, as expected. 44/44 assertions with the fix. Device path not run (no GPU here).