From 1a614b2e38b0ebc231bc9ab42317f4434213f1b4 Mon Sep 17 00:00:00 2001 From: Tyler Sax Date: Tue, 16 Jun 2026 15:57:07 -0500 Subject: [PATCH 1/3] Symmetrization M0: add characterization harness, capture multi-band bug Add a per-(model, point-group) characterization harness for single-particle cluster symmetrization, generalizing the existing identity-only symmetrize_test onto real point groups. It is the regression net for upcoming Hamiltonian- derived symmetrization work. Oracle: G0 = [iw + mu - H0]^-1 is deterministic and exactly invariant under every symmetry of H0, so symmetrizing G0 must be a no-op (asserted to 1e-12). Note: this harness flagged a failure the first time it ran, and we are capturing that failure on purpose. Production Symmetrize::execute is not a no-op on the three-band Hubbard model with D4 symmetry. Given the exact, deterministic oracle, the failure is treated as an existing defect in the imposition. Rather than commit a red test, the observed failures are encoded as the expected result. This keeps CI green and makes the test fixture both the record of the bug and the gate for its fix: when the imposition is corrected the assertion flips (forcing the test to be cleaned up). Structure: four tests per case, one binary per case selected via CMake TEST_DEFINES. square/D4 and singleband-chain are clean green baselines; three-band/D4 carries the pinned failure above. --- .../dca_step/symmetrization/CMakeLists.txt | 31 ++ .../singleband_chain_input.json | 31 ++ .../symmetrization/square_D4_input.json | 31 ++ .../symmetrize_characterization_test.cpp | 425 ++++++++++++++++++ .../symmetrization/threeband_D4_input.json | 35 ++ 5 files changed, 553 insertions(+) create mode 100644 test/unit/phys/dca_step/symmetrization/singleband_chain_input.json create mode 100644 test/unit/phys/dca_step/symmetrization/square_D4_input.json create mode 100644 test/unit/phys/dca_step/symmetrization/symmetrize_characterization_test.cpp create mode 100644 test/unit/phys/dca_step/symmetrization/threeband_D4_input.json diff --git a/test/unit/phys/dca_step/symmetrization/CMakeLists.txt b/test/unit/phys/dca_step/symmetrization/CMakeLists.txt index 709a60918..8fd67a45d 100644 --- a/test/unit/phys/dca_step/symmetrization/CMakeLists.txt +++ b/test/unit/phys/dca_step/symmetrization/CMakeLists.txt @@ -6,3 +6,34 @@ dca_add_gtest(symmetrize_test INCLUDE_DIRS ${DCA_INCLUDES};${PROJECT_SOURCE_DIR};${FFTW_INCLUDE_DIR} LIBS ${DCA_LIBS} ) + +# PR-0 characterization harness. One binary per (lattice, point group) case: the +# DCA domain + cluster-symmetry singletons are keyed on dimension/role, not on the +# lattice, so cases of the same dimension cannot coexist in one process. Each case +# is selected from the shared source via THIS_TEST_DEFINES (CMake TEST_DEFINES). +dca_add_gtest(symmetrize_characterization_square_D4_test + FAST + GTEST_MPI_MAIN + INCLUDE_DIRS ${DCA_INCLUDES};${PROJECT_SOURCE_DIR};${FFTW_INCLUDE_DIR} + LIBS ${DCA_LIBS} + CUSTOM_SOURCE symmetrize_characterization_test.cpp + TEST_DEFINES SquareD4 + ) + +dca_add_gtest(symmetrize_characterization_threeband_D4_test + FAST + GTEST_MPI_MAIN + INCLUDE_DIRS ${DCA_INCLUDES};${PROJECT_SOURCE_DIR};${FFTW_INCLUDE_DIR} + LIBS ${DCA_LIBS} + CUSTOM_SOURCE symmetrize_characterization_test.cpp + TEST_DEFINES ThreebandD4 + ) + +dca_add_gtest(symmetrize_characterization_singleband_chain_test + FAST + GTEST_MPI_MAIN + INCLUDE_DIRS ${DCA_INCLUDES};${PROJECT_SOURCE_DIR};${FFTW_INCLUDE_DIR} + LIBS ${DCA_LIBS} + CUSTOM_SOURCE symmetrize_characterization_test.cpp + TEST_DEFINES SinglebandChain + ) diff --git a/test/unit/phys/dca_step/symmetrization/singleband_chain_input.json b/test/unit/phys/dca_step/symmetrization/singleband_chain_input.json new file mode 100644 index 000000000..57d9ecd54 --- /dev/null +++ b/test/unit/phys/dca_step/symmetrization/singleband_chain_input.json @@ -0,0 +1,31 @@ +{ + "physics": { + "beta" : 1., + "chemical-potential" : 0. + }, + + "singleband-chain-model": + { + "t" : 1., + "U" : 4. + }, + + "domains": { + "real-space-grids": { + "cluster": [[2, 0], [0, 2]], + "sp-host": [[8, 0], [0, 8]] + }, + + "imaginary-time": { + "sp-time-intervals": 128 + }, + + "imaginary-frequency": { + "sp-fermionic-frequencies": 64 + } + }, + + "DCA": { + "interacting-orbitals": [0] + } +} diff --git a/test/unit/phys/dca_step/symmetrization/square_D4_input.json b/test/unit/phys/dca_step/symmetrization/square_D4_input.json new file mode 100644 index 000000000..65bd57b2c --- /dev/null +++ b/test/unit/phys/dca_step/symmetrization/square_D4_input.json @@ -0,0 +1,31 @@ +{ + "physics": { + "beta" : 1., + "chemical-potential" : 0. + }, + + "single-band-Hubbard-model": + { + "t" : 1., + "U" : 4. + }, + + "domains": { + "real-space-grids": { + "cluster": [[2, 0], [0, 2]], + "sp-host": [[8, 0], [0, 8]] + }, + + "imaginary-time": { + "sp-time-intervals": 128 + }, + + "imaginary-frequency": { + "sp-fermionic-frequencies": 64 + } + }, + + "DCA": { + "interacting-orbitals": [0] + } +} diff --git a/test/unit/phys/dca_step/symmetrization/symmetrize_characterization_test.cpp b/test/unit/phys/dca_step/symmetrization/symmetrize_characterization_test.cpp new file mode 100644 index 000000000..860482f33 --- /dev/null +++ b/test/unit/phys/dca_step/symmetrization/symmetrize_characterization_test.cpp @@ -0,0 +1,425 @@ +// Copyright (C) 2018 ETH Zurich +// Copyright (C) 2018 UT-Battelle, LLC +// All rights reserved. +// +// See LICENSE for terms of usage. +// See CITATION.md for citation guidelines, if DCA++ is used for scientific publications. +// +// Author: Tyler Sax (tylersax@gmail.com) +// +// Characterization harness for single-particle cluster symmetrization. +// +// This is the regression net for Hamiltonian-derived-symmetrization. It +// generalizes symmetrize_test.cpp from one trivial-symmetry case (twoband_chain x +// no_symmetry<2>, an identity-only group that makes every cluster loop a structural +// no-op) into a per-(model, operation) characterization across lattices paired with +// their real point groups. +// +// Oracle: G0 = [iw + mu - H0]^-1 is deterministic and exactly invariant under every +// symmetry S of H0, so symmetrizing G0 must be a no-op. Four tests exercise this: +// 1. DomainActivation: assert the cluster realized the expected number of symmetry +// ops, so the no-op checks below have a non-trivial group to exercise. +// 2. CoarseNoOp: run the real production Symmetrize::execute on a copy of G0 and diff +// against the original at ~1e-12, over all four representations (k/r x w/t). This +// is the authoritative regression net -- it runs the whole production pipeline, +// which is where the multi-orbital bugs live. +// 3 & 4. PerOpMapMomentum / PerOpMapRealSpace: a test-local applicator that, for one +// operation index, applies today's cluster-symmetry table relation to G0 and checks +// invariance. They mirror executeCluster, so they characterize today's +// representation+code per operation -- isolating which op's encoding is to blame. +// +// Limitation: A no-op on already-symmetric G0 detects operations whose encoding +// is wrong, but does not detect symmetry operations that are silently missing. +// +// Test architecture: One binary per case. The cluster_symmetry> table +// and all DCA domains are singletons keyed on (scalar, D, role-NAME, rep, shape) -- not on +// the lattice -- so two lattices of the same dimension alias the same singletons and a +// second update_domains() would clobber the first. Each case is therefore built into its +// own binary, selected by THIS_TEST_DEFINES via CMake TEST_DEFINES (same idiom as +// tp_accumulator_gpu_test.cpp). + +#include "dca/platform/dca_gpu.h" +#include "dca/phys/dca_step/symmetrization/symmetrize.hpp" + +#include +#include +#include +#include +#include + +#include "dca/testing/gtest_h_w_warning_blocking.h" + +#include "dca/config/threading.hpp" +#include "dca/io/json/json_reader.hpp" +#include "dca/function/util/real_complex_conversion.hpp" +#include "dca/parallel/no_concurrency/no_concurrency.hpp" +#include "dca/phys/dca_algorithms/compute_free_greens_function.hpp" +#include "dca/phys/domains/cluster/cluster_symmetry.hpp" +#include "dca/phys/domains/cluster/symmetries/point_groups/2d/2d_square.hpp" +#include "dca/phys/domains/cluster/symmetries/point_groups/no_symmetry.hpp" +#include "dca/phys/parameters/parameters.hpp" +#include "dca/profiling/null_profiler.hpp" +#include "dca/phys/models/analytic_hamiltonians/singleband_chain.hpp" +#include "dca/phys/models/analytic_hamiltonians/square_lattice.hpp" +#include "dca/phys/models/analytic_hamiltonians/threeband_hubbard.hpp" +#include "dca/phys/models/tight_binding_model.hpp" +#include "dca/phys/parameters/num_traits.hpp" + +namespace { + +const std::string input_dir = DCA_SOURCE_DIR "/test/unit/phys/dca_step/symmetrization/"; + +// Double-precision tolerance for the deterministic G0 oracle +constexpr double kTolerance = 1e-12; + +using Concurrency = dca::parallel::NoConcurrency; + +using dca::func::dmn_0; +using dca::func::dmn_variadic; +using dca::func::function; +using BDmn = dmn_0; +using SDmn = dmn_0; +using NuDmn = dmn_variadic; + +// --------------------------------------------------------------------------------- +// Case traits. THIS_TEST_DEFINES (set by CMake TEST_DEFINES) selects which one is +// instantiated; only one trait is ever live per binary, so the domain singletons are +// never shared across cases. +// +// The expectedFailing*() lists hold the ops/reps whose encoding the per-op oracle +// finds inconsistent with the deterministic G0 -- i.e. the committed red cells. They +// are filled from the harness's own first run and become the gate: any change flips the +// test. (Empty == the whole point-group is encoded correctly for this representation.) +// --------------------------------------------------------------------------------- + +// CASE 1 -- square lattice with the full D4 point group (8 ops) +struct SquareD4 { + using Scalar = double; + using Lattice = dca::phys::models::square_lattice; + static constexpr char Input[] = "square_D4_input.json"; + static constexpr int expected_num_symmetries = 8; + // GREEN baseline: single band, no off-diagonals -- nothing for the imposition to + // get wrong. Validates that the harness itself reports green when it should. + static std::vector expectedFailingReps() { return {}; } + static std::vector expectedFailingKOps() { return {}; } + static std::vector expectedFailingROps() { return {}; } +}; + +// CASE 2 -- three-band Hubbard (Emery/CuO2) model on D4. This case fails the +// newly-introduced test, indicating a possible bug we expect to fix in the next +// milestone of the symmetrization project. +struct ThreebandD4 { + using Scalar = double; + using Lattice = dca::phys::models::ThreebandHubbard; + static constexpr char Input[] = "threeband_D4_input.json"; + static constexpr int expected_num_symmetries = 8; + // The per-op cluster maps are GREEN -- each single operation's table encoding is + // consistent with G0 -- but the production Symmetrize::execute is NOT a no-op on + // the deterministic G0, suggesting buggy behavior in how multi-band symmetry + // is imposed. These flip to green when the next milestone in the symmetrization + // project rewrites the imposition. + static std::vector expectedFailingReps() { return {"k_iw", "r_iw", "r_tau"}; } + static std::vector expectedFailingKOps() { return {}; } + static std::vector expectedFailingROps() { return {}; } +}; + +// CASE 3 -- 1D chain. Since this model has identity-only symmetry, this test exercises +// only the basic plumbing (valuable as a diagnostic). +struct SinglebandChain { + using Scalar = double; + // singleband_chain hardcodes no_symmetry<2> regardless of the template argument. + using Lattice = dca::phys::models::singleband_chain>; + static constexpr char Input[] = "singleband_chain_input.json"; + static constexpr int expected_num_symmetries = 1; + // GREEN baseline: identity-only group, single band. + static std::vector expectedFailingReps() { return {}; } + static std::vector expectedFailingKOps() { return {}; } + static std::vector expectedFailingROps() { return {}; } +}; + +// Templated test fixture +template +class SymmetrizeCharacterizationTest : public ::testing::Test { +protected: + using Scalar = typename C::Scalar; + using Lattice = typename C::Lattice; + using Model = dca::phys::models::TightBindingModel; + + using Parameters = + dca::phys::params::Parameters, Scalar>>; + + using RClusterDmn = typename Parameters::RClusterDmn; + using KClusterDmn = typename Parameters::KClusterDmn; + using TDmn = typename Parameters::TDmn; + using WDmn = typename Parameters::WDmn; + + using KCluster = typename KClusterDmn::parameter_type; + using RCluster = typename RClusterDmn::parameter_type; + using KSymDmn = typename dca::phys::domains::cluster_symmetry::sym_super_cell_dmn_t; + using RSymDmn = typename dca::phys::domains::cluster_symmetry::sym_super_cell_dmn_t; + + SymmetrizeCharacterizationTest() : concurrency_(0, nullptr), parameters_("", concurrency_) { + // load the per-case JSON into parameters_, derive model-level data. + parameters_.template read_input_and_broadcast(input_dir + C::Input); + parameters_.update_model(); + + // Domain singletons are process-global and must be initialized exactly once, even + // though gtest builds a fresh fixture per test. This static guard lets multiple + // TYPED_TESTs share one init in a single binary -- tp_accumulator_gpu_test.cpp hits + // the same singleton constraint but works around it by only ever running one case + // per binary. reset() is inside the guard because only the first fixture's members + // were constructed before update_domains() ran (so only they are mis-sized); the H0/ + // H_symmetry fills are outside because every fixture gets fresh, zero-valued members. + static bool domain_initialized = false; + if (!domain_initialized) { + parameters_.update_domains(); + H0_.reset(); + H_symmetry_.reset(); + domain_initialized = true; + } + + Parameters::model_type::initializeH0(parameters_, H0_); + Parameters::model_type::initialize_H_symmetries(H_symmetry_); + } + + Concurrency concurrency_; + Parameters parameters_; + + function, dmn_variadic> H0_; + function> H_symmetry_; +}; + +using TestTypes = ::testing::Types; +TYPED_TEST_CASE(SymmetrizeCharacterizationTest, TestTypes); + +// Largest element-wise magnitude of (a - b) over two identically-shaped functions. +template +double maxAbsDiff(const F& a, const F& b) { + double m = 0.; + // std::abs correctly returns the modulus for std::complex. + for (int i = 0; i < a.size(); ++i) + m = std::max(m, std::abs(a(i) - b(i))); + return m; +} + +// TEST 1 of 4: DomainActivation -- sanity gate. Reads the op counts off the symmetry +// domains and asserts they match the trait's expected_num_symmetries. Without this, a +// misconfigured case could run green simply because it had only the identity op to check. +TYPED_TEST(SymmetrizeCharacterizationTest, DomainActivation) { + using Fixture = SymmetrizeCharacterizationTest; + const int n_k_ops = Fixture::KSymDmn::dmn_size(); + const int n_r_ops = Fixture::RSymDmn::dmn_size(); + + std::cout << "[case] " << TypeParam::Input << ": cluster sym ops = " << n_k_ops + << " (momentum), " << n_r_ops << " (real space); bands = " << BDmn::dmn_size() + << ", k-points = " << Fixture::KClusterDmn::dmn_size() << "\n"; + + EXPECT_EQ(n_k_ops, TypeParam::expected_num_symmetries); + EXPECT_EQ(n_r_ops, TypeParam::expected_num_symmetries); +} + +// TEST 2 of 4: CoarseNoOp -- the full production pipeline, asserted at 1e-12. Covers the +// four representations the existing test exercises (k/r x w/t). For each, run +// Symmetrize::execute on a copy of G0 and diff against the original. The set of +// representations that are not left invariant is compared to the expected failing set +// declared in the case trait, so all cases stay green now while the known-red cells gate +// the next milestone of the symmetrization project. +// +// Unlike the per-op maps, this runs the whole production pipeline (spin + cluster + +// time/frequency channels and the averaging/norm), so it catches defects in how the +// operations are combined. +TYPED_TEST(SymmetrizeCharacterizationTest, CoarseNoOp) { + using Fixture = SymmetrizeCharacterizationTest; + using Parameters = typename Fixture::Parameters; + using KClusterDmn = typename Fixture::KClusterDmn; + using RClusterDmn = typename Fixture::RClusterDmn; + using TDmn = typename Fixture::TDmn; + using WDmn = typename Fixture::WDmn; + + // Symmetrize a copy with the production code; return max element-wise change. + auto noOpResidual = [](auto G0) { + auto sym = G0; + dca::phys::Symmetrize::execute(sym, false); + return maxAbsDiff(G0, sym); + }; + + function, dmn_variadic> G0_k_w; + dca::phys::compute_G0_k_w(this->H0_, this->parameters_.get_chemical_potential(), 1, G0_k_w); + + function, dmn_variadic> G0_r_w; + dca::math::transform::FunctionTransform::execute(G0_k_w, G0_r_w); + + function, dmn_variadic> G0_k_t; + dca::phys::compute_G0_k_t(this->H0_, this->parameters_.get_chemical_potential(), + this->parameters_.get_beta(), G0_k_t); + + function, dmn_variadic> G0_r_t_cmplx; + dca::math::transform::FunctionTransform::execute(G0_k_t, G0_r_t_cmplx); + function> G0_r_t; + G0_r_t = dca::func::util::real(G0_r_t_cmplx, dca::ImagCheck::FAIL); + + // Run the oracle on all four reps and collect (name, residual) pairs + const std::vector> residuals = { + {"k_iw", noOpResidual(G0_k_w)}, + {"r_iw", noOpResidual(G0_r_w)}, + {"k_tau", noOpResidual(G0_k_t)}, + {"r_tau", noOpResidual(G0_r_t)}}; + + // Turn residuals into a red/green list and diff against the expected failing set + std::vector failing_reps; + std::cout << "[coarse no-op residuals] " << TypeParam::Input << "\n"; + for (const auto& [rep, residual] : residuals) { + const bool failed = residual >= kTolerance; + std::cout << " " << rep << ": " << (failed ? "FAIL" : "PASS") + << " max|G0 - sym(G0)| = " << residual << "\n"; + if (failed) + failing_reps.push_back(rep); + } + + // Compare as a set: sort both sides so the assertion does not depend on the residuals + // iteration order (which carries no meaning). + std::sort(failing_reps.begin(), failing_reps.end()); + auto expected_reps = TypeParam::expectedFailingReps(); + std::sort(expected_reps.begin(), expected_reps.end()); + EXPECT_EQ(failing_reps, expected_reps) + << "Coarse no-op red/green map changed vs the expected failing set."; +} + +// TEST 3 of 4: PerOpMapMomentum -- per-operation map (momentum channel). For each +// operation s, mirror the momentum band-aware executeCluster relation on G0(k, iw): +// G0(b0, b1, k) == sign_s * G0(b0_new, b1_new, k_new) when sign_s != 0, +// where (k_new, b*_new) come from get_symmetry_matrix() and sign_s from +// transformationSignOfK. The set of ops with a real violation is compared to +// the expected failing set. +TYPED_TEST(SymmetrizeCharacterizationTest, PerOpMapMomentum) { + using Fixture = SymmetrizeCharacterizationTest; + using KClusterDmn = typename Fixture::KClusterDmn; + using WDmn = typename Fixture::WDmn; + using KCluster = typename Fixture::KCluster; + using Lattice = typename Fixture::Lattice; + + // Build the (k, iw) oracle exactly as in CoarseNoOp. + function, dmn_variadic> G0; + dca::phys::compute_G0_k_w(this->H0_, this->parameters_.get_chemical_potential(), 1, G0); + + const auto& sym = dca::phys::domains::cluster_symmetry::get_symmetry_matrix(); + const int n_ops = Fixture::KSymDmn::dmn_size(); + const int nb = BDmn::dmn_size(); + const int ns = SDmn::dmn_size(); + const int nk = KClusterDmn::dmn_size(); + const int nw = WDmn::dmn_size(); + + std::vector failing_ops; + std::cout << "[per-op momentum map] " << TypeParam::Input << "\n"; + // ---- Outer loop over symmetry ops s. Each iteration is one independent check. + for (int s = 0; s < n_ops; ++s) { + double max_viol = 0.; + bool any_checked = false; // Tracks whether op s constrained anything (else N/A) + // ---- Sweep every (k, b0, b1) band-pair at this op + for (int k = 0; k < nk; ++k) + for (int b0 = 0; b0 < nb; ++b0) + for (int b1 = 0; b1 < nb; ++b1) { + const double sign = Lattice::transformationSignOfK(b0, b1, s); + if (sign == 0) // op does not constrain this band pair -> N/A + continue; + const int k_new = sym(k, b0, s).first; // mirrors production ("FIXME: b0 -> b1") + const int b0_new = sym(k, b0, s).second; + const int b1_new = sym(k, b1, s).second; + // ---- Invariance check, swept over spin and frequency. Asserts + // G0(b0,b1,k,w) == sign * G0(b0_new,b1_new,k_new,w). Accumulate worst + // violation rather than failing on first, so the printed number is the + // real max error for this op. + for (int sp = 0; sp < ns; ++sp) + for (int w = 0; w < nw; ++w) { + any_checked = true; + const auto lhs = G0(b0, sp, b1, sp, k, w); + const auto rhs = sign * G0(b0_new, sp, b1_new, sp, k_new, w); + max_viol = std::max(max_viol, std::abs(lhs - rhs)); + } + } + // Classify this op: FAIL only if it actually checked something AND exceeded tolerance + const bool failed = any_checked && max_viol >= kTolerance; + std::cout << " op " << s << ": " << (any_checked ? (failed ? "FAIL" : "PASS") : "N/A ") + << " max|G0 - S.G0| = " << max_viol << "\n"; + if (failed) + failing_ops.push_back(s); + } + + std::sort(failing_ops.begin(), failing_ops.end()); + auto expected_ops = TypeParam::expectedFailingKOps(); + std::sort(expected_ops.begin(), expected_ops.end()); + EXPECT_EQ(failing_ops, expected_ops) + << "Momentum per-op red/green map changed vs the expected failing set."; +} + +// TEST 4 of 4: PerOpMapRealSpace -- mirror of TEST 3 but in real space / tau, and with one +// key difference: it skips off-diagonal band pairs (b0!=b1) because production's real-space +// executeCluster does the same. That skip is itself one of the suspected bugs, so this test +// characterizes production's behavior, off-diag blind spot included. +TYPED_TEST(SymmetrizeCharacterizationTest, PerOpMapRealSpace) { + using Fixture = SymmetrizeCharacterizationTest; + using KClusterDmn = typename Fixture::KClusterDmn; + using RClusterDmn = typename Fixture::RClusterDmn; + using TDmn = typename Fixture::TDmn; + using RCluster = typename Fixture::RCluster; + using Lattice = typename Fixture::Lattice; + + // Build (k,tau), FT to (r,tau), drop the imaginary part -> real G0(r,tau). + function, dmn_variadic> G0_k_t; + dca::phys::compute_G0_k_t(this->H0_, this->parameters_.get_chemical_potential(), + this->parameters_.get_beta(), G0_k_t); + function, dmn_variadic> G0_cmplx; + dca::math::transform::FunctionTransform::execute(G0_k_t, G0_cmplx); + function> G0; + G0 = dca::func::util::real(G0_cmplx, dca::ImagCheck::FAIL); + + const auto& sym = dca::phys::domains::cluster_symmetry::get_symmetry_matrix(); + const int n_ops = Fixture::RSymDmn::dmn_size(); + const int nb = BDmn::dmn_size(); + const int ns = SDmn::dmn_size(); + const int nr = RClusterDmn::dmn_size(); + const int nt = TDmn::dmn_size(); + + std::vector failing_ops; + std::cout << "[per-op real-space map] " << TypeParam::Input << "\n"; + for (int s = 0; s < n_ops; ++s) { + double max_viol = 0.; + bool any_checked = false; + for (int r = 0; r < nr; ++r) + for (int b0 = 0; b0 < nb; ++b0) + for (int b1 = 0; b1 < nb; ++b1) { + if (b0 != b1) // off-diagonal skip in executeCluster -> N/A + continue; + // Real-space phase for this diagonal element under op s; 0 => N/A. + const double sign = Lattice::transformationSignOfR(b0, b1, s); + if (sign == 0) + continue; + const int r_new = sym(r, 0, s).first; + const int b0_new = sym(r, b0, s).second; + const int b1_new = sym(0, b1, s).second; + // Invariance check over spin + imaginary time, real arithmetic this time. + for (int sp = 0; sp < ns; ++sp) + for (int t = 0; t < nt; ++t) { + any_checked = true; + const double lhs = G0(b0, sp, b1, sp, r, t); + const double rhs = sign * G0(b0_new, sp, b1_new, sp, r_new, t); + max_viol = std::max(max_viol, std::abs(lhs - rhs)); + } + } + const bool failed = any_checked && max_viol >= kTolerance; + std::cout << " op " << s << ": " << (any_checked ? (failed ? "FAIL" : "PASS") : "N/A ") + << " max|G0 - S.G0| = " << max_viol << "\n"; + if (failed) + failing_ops.push_back(s); + } + + std::sort(failing_ops.begin(), failing_ops.end()); + auto expected_ops = TypeParam::expectedFailingROps(); + std::sort(expected_ops.begin(), expected_ops.end()); + EXPECT_EQ(failing_ops, expected_ops) + << "Real-space per-op red/green map changed vs the expected failing set."; +} + +} // namespace diff --git a/test/unit/phys/dca_step/symmetrization/threeband_D4_input.json b/test/unit/phys/dca_step/symmetrization/threeband_D4_input.json new file mode 100644 index 000000000..0665343c2 --- /dev/null +++ b/test/unit/phys/dca_step/symmetrization/threeband_D4_input.json @@ -0,0 +1,35 @@ +{ + "physics": { + "beta" : 1., + "chemical-potential" : 0. + }, + + "threebands-Hubbard-model": + { + "t_pd" : 1., + "t_pp" : 0.5, + "ep_d" : 0., + "ep_p" : 3., + "U_dd" : 8., + "U_pp" : 0. + }, + + "domains": { + "real-space-grids": { + "cluster": [[2, 0], [0, 2]], + "sp-host": [[8, 0], [0, 8]] + }, + + "imaginary-time": { + "sp-time-intervals": 128 + }, + + "imaginary-frequency": { + "sp-fermionic-frequencies": 64 + } + }, + + "DCA": { + "interacting-orbitals": [0, 1, 2] + } +} From 24c227c5fa135a8153334028e209a6ca62b6ef4e Mon Sep 17 00:00:00 2001 From: Tyler Sax Date: Wed, 17 Jun 2026 14:12:44 -0500 Subject: [PATCH 2/3] Update comment for clarity --- test/unit/phys/dca_step/symmetrization/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/phys/dca_step/symmetrization/CMakeLists.txt b/test/unit/phys/dca_step/symmetrization/CMakeLists.txt index 8fd67a45d..1c0c3da9d 100644 --- a/test/unit/phys/dca_step/symmetrization/CMakeLists.txt +++ b/test/unit/phys/dca_step/symmetrization/CMakeLists.txt @@ -7,10 +7,10 @@ dca_add_gtest(symmetrize_test LIBS ${DCA_LIBS} ) -# PR-0 characterization harness. One binary per (lattice, point group) case: the -# DCA domain + cluster-symmetry singletons are keyed on dimension/role, not on the -# lattice, so cases of the same dimension cannot coexist in one process. Each case -# is selected from the shared source via THIS_TEST_DEFINES (CMake TEST_DEFINES). +# Test binaries for derived symmetrization. One binary per (lattice, point group) +# case: the DCA domain + cluster-symmetry singletons are keyed on dimension/role, +# not on the lattice, so cases of the same dimension cannot coexist in one process. +# Each case is selected from the shared source via TEST_DEFINES. dca_add_gtest(symmetrize_characterization_square_D4_test FAST GTEST_MPI_MAIN From 9d95cb439381d037ec3d46821f87107467776da9 Mon Sep 17 00:00:00 2001 From: Tyler Sax Date: Mon, 22 Jun 2026 08:30:33 -0500 Subject: [PATCH 3/3] recharacterizes symmetrization test as integration --- test/integration/CMakeLists.txt | 1 + .../phys/symmetrization/CMakeLists.txt | 32 +++++++++++++++++++ .../singleband_chain_input.json | 0 .../phys}/symmetrization/square_D4_input.json | 0 .../symmetrize_characterization_test.cpp | 2 +- .../symmetrization/threeband_D4_input.json | 0 .../dca_step/symmetrization/CMakeLists.txt | 31 ------------------ 7 files changed, 34 insertions(+), 32 deletions(-) create mode 100644 test/integration/phys/symmetrization/CMakeLists.txt rename test/{unit/phys/dca_step => integration/phys}/symmetrization/singleband_chain_input.json (100%) rename test/{unit/phys/dca_step => integration/phys}/symmetrization/square_D4_input.json (100%) rename test/{unit/phys/dca_step => integration/phys}/symmetrization/symmetrize_characterization_test.cpp (99%) rename test/{unit/phys/dca_step => integration/phys}/symmetrization/threeband_D4_input.json (100%) diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt index f2ae18d59..35dd96adf 100644 --- a/test/integration/CMakeLists.txt +++ b/test/integration/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(exact_diagonalization_advanced) add_subdirectory(fourier_transform) add_subdirectory(interpolation) add_subdirectory(phys/lattice_mapping) +add_subdirectory(phys/symmetrization) add_subdirectory(math/statistical_testing) add_subdirectory(nfft) add_subdirectory(parallel/mpi_concurrency) diff --git a/test/integration/phys/symmetrization/CMakeLists.txt b/test/integration/phys/symmetrization/CMakeLists.txt new file mode 100644 index 000000000..fd3ba98d8 --- /dev/null +++ b/test/integration/phys/symmetrization/CMakeLists.txt @@ -0,0 +1,32 @@ +# Symmetrization characterization harness + +# Test binaries for derived symmetrization. One binary per (lattice, point group) +# case: the DCA domain + cluster-symmetry singletons are keyed on dimension/role, +# not on the lattice, so cases of the same dimension cannot coexist in one process. +# Each case is selected from the shared source via TEST_DEFINES. +dca_add_gtest(symmetrize_characterization_square_D4_test + FAST + GTEST_MPI_MAIN + INCLUDE_DIRS ${DCA_INCLUDES};${PROJECT_SOURCE_DIR};${FFTW_INCLUDE_DIR} + LIBS ${DCA_LIBS} + CUSTOM_SOURCE symmetrize_characterization_test.cpp + TEST_DEFINES SquareD4 + ) + +dca_add_gtest(symmetrize_characterization_threeband_D4_test + FAST + GTEST_MPI_MAIN + INCLUDE_DIRS ${DCA_INCLUDES};${PROJECT_SOURCE_DIR};${FFTW_INCLUDE_DIR} + LIBS ${DCA_LIBS} + CUSTOM_SOURCE symmetrize_characterization_test.cpp + TEST_DEFINES ThreebandD4 + ) + +dca_add_gtest(symmetrize_characterization_singleband_chain_test + FAST + GTEST_MPI_MAIN + INCLUDE_DIRS ${DCA_INCLUDES};${PROJECT_SOURCE_DIR};${FFTW_INCLUDE_DIR} + LIBS ${DCA_LIBS} + CUSTOM_SOURCE symmetrize_characterization_test.cpp + TEST_DEFINES SinglebandChain + ) diff --git a/test/unit/phys/dca_step/symmetrization/singleband_chain_input.json b/test/integration/phys/symmetrization/singleband_chain_input.json similarity index 100% rename from test/unit/phys/dca_step/symmetrization/singleband_chain_input.json rename to test/integration/phys/symmetrization/singleband_chain_input.json diff --git a/test/unit/phys/dca_step/symmetrization/square_D4_input.json b/test/integration/phys/symmetrization/square_D4_input.json similarity index 100% rename from test/unit/phys/dca_step/symmetrization/square_D4_input.json rename to test/integration/phys/symmetrization/square_D4_input.json diff --git a/test/unit/phys/dca_step/symmetrization/symmetrize_characterization_test.cpp b/test/integration/phys/symmetrization/symmetrize_characterization_test.cpp similarity index 99% rename from test/unit/phys/dca_step/symmetrization/symmetrize_characterization_test.cpp rename to test/integration/phys/symmetrization/symmetrize_characterization_test.cpp index 860482f33..b80d1b4d8 100644 --- a/test/unit/phys/dca_step/symmetrization/symmetrize_characterization_test.cpp +++ b/test/integration/phys/symmetrization/symmetrize_characterization_test.cpp @@ -67,7 +67,7 @@ namespace { -const std::string input_dir = DCA_SOURCE_DIR "/test/unit/phys/dca_step/symmetrization/"; +const std::string input_dir = DCA_SOURCE_DIR "/test/integration/phys/symmetrization/"; // Double-precision tolerance for the deterministic G0 oracle constexpr double kTolerance = 1e-12; diff --git a/test/unit/phys/dca_step/symmetrization/threeband_D4_input.json b/test/integration/phys/symmetrization/threeband_D4_input.json similarity index 100% rename from test/unit/phys/dca_step/symmetrization/threeband_D4_input.json rename to test/integration/phys/symmetrization/threeband_D4_input.json diff --git a/test/unit/phys/dca_step/symmetrization/CMakeLists.txt b/test/unit/phys/dca_step/symmetrization/CMakeLists.txt index 1c0c3da9d..709a60918 100644 --- a/test/unit/phys/dca_step/symmetrization/CMakeLists.txt +++ b/test/unit/phys/dca_step/symmetrization/CMakeLists.txt @@ -6,34 +6,3 @@ dca_add_gtest(symmetrize_test INCLUDE_DIRS ${DCA_INCLUDES};${PROJECT_SOURCE_DIR};${FFTW_INCLUDE_DIR} LIBS ${DCA_LIBS} ) - -# Test binaries for derived symmetrization. One binary per (lattice, point group) -# case: the DCA domain + cluster-symmetry singletons are keyed on dimension/role, -# not on the lattice, so cases of the same dimension cannot coexist in one process. -# Each case is selected from the shared source via TEST_DEFINES. -dca_add_gtest(symmetrize_characterization_square_D4_test - FAST - GTEST_MPI_MAIN - INCLUDE_DIRS ${DCA_INCLUDES};${PROJECT_SOURCE_DIR};${FFTW_INCLUDE_DIR} - LIBS ${DCA_LIBS} - CUSTOM_SOURCE symmetrize_characterization_test.cpp - TEST_DEFINES SquareD4 - ) - -dca_add_gtest(symmetrize_characterization_threeband_D4_test - FAST - GTEST_MPI_MAIN - INCLUDE_DIRS ${DCA_INCLUDES};${PROJECT_SOURCE_DIR};${FFTW_INCLUDE_DIR} - LIBS ${DCA_LIBS} - CUSTOM_SOURCE symmetrize_characterization_test.cpp - TEST_DEFINES ThreebandD4 - ) - -dca_add_gtest(symmetrize_characterization_singleband_chain_test - FAST - GTEST_MPI_MAIN - INCLUDE_DIRS ${DCA_INCLUDES};${PROJECT_SOURCE_DIR};${FFTW_INCLUDE_DIR} - LIBS ${DCA_LIBS} - CUSTOM_SOURCE symmetrize_characterization_test.cpp - TEST_DEFINES SinglebandChain - )