From 7b25f29dd2d8c86a93b4b60bc5a4e5c512e03948 Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Fri, 9 Oct 2026 16:55:16 +0800 Subject: [PATCH 1/5] fix: refresh periodic ghost image shifts with skin --- .../module_neighlist/domain_decomposition.cpp | 57 +++++++++++++++--- .../module_neighlist/domain_decomposition.h | 2 + .../test/mdcell_migrate_mpi_test.cpp | 58 +++++++++++++++++++ 3 files changed, 109 insertions(+), 8 deletions(-) diff --git a/source/source_cell/module_neighlist/domain_decomposition.cpp b/source/source_cell/module_neighlist/domain_decomposition.cpp index 15eb7c5eedc..8d77229c680 100644 --- a/source/source_cell/module_neighlist/domain_decomposition.cpp +++ b/source/source_cell/module_neighlist/domain_decomposition.cpp @@ -330,6 +330,43 @@ bool DomainDecomposition::atom_overlaps_target_halo( return true; } +std::array DomainDecomposition::image_shift_for_update( + const LocalAtom& atom, + const GhostExchangeSlot& slot) const +{ + std::array image_shift = slot.image_shift; + const double frac[3] = {atom.frac.x, atom.frac.y, atom.frac.z}; + for (int idim = 0; idim < 3; ++idim) + { + // For a one-domain periodic direction, retain the image side of the + // slot so a wrapped atom does not become a duplicate of itself. + if (dims_[idim] == 1 && slot.offset[idim] != 0) + { + if (slot.offset[idim] > 0) + { + image_shift[idim] = frac[idim] < margin_[idim] ? 1 : -1; + } + else + { + image_shift[idim] = frac[idim] > 1.0 - margin_[idim] ? -1 : 1; + } + continue; + } + + const double lo = static_cast(slot.target_coords[idim]) / dims_[idim]; + const double hi = static_cast(slot.target_coords[idim] + 1) / dims_[idim]; + while (frac[idim] + image_shift[idim] < lo - margin_[idim]) + { + ++image_shift[idim]; + } + while (frac[idim] + image_shift[idim] >= hi + margin_[idim]) + { + --image_shift[idim]; + } + } + return image_shift; +} + int DomainDecomposition::neighbor_layer(int dim) const { return std::max(1, static_cast(std::ceil(margin_[dim] * dims_[dim]))); @@ -610,15 +647,21 @@ void DomainDecomposition::update_ghost_atom_positions(const std::vector send_frac(3 * slot.send_atom_indices.size(), 0.0); + // Keep the wrapped fractional coordinates for metadata and send the + // dynamically selected image coordinates in the same message. + std::vector send_frac(6 * slot.send_atom_indices.size(), 0.0); for (std::size_t i = 0; i < slot.send_atom_indices.size(); ++i) { const LocalAtom& atom = owned_atoms[static_cast(slot.send_atom_indices[i])]; + const std::array image_shift = image_shift_for_update(atom, slot); send_frac[3 * i] = atom.frac.x; send_frac[3 * i + 1] = atom.frac.y; send_frac[3 * i + 2] = atom.frac.z; + send_frac[3 * slot.send_atom_indices.size() + 3 * i] = atom.frac.x + image_shift[0]; + send_frac[3 * slot.send_atom_indices.size() + 3 * i + 1] = atom.frac.y + image_shift[1]; + send_frac[3 * slot.send_atom_indices.size() + 3 * i + 2] = atom.frac.z + image_shift[2]; } - std::vector recv_frac(3 * static_cast(slot.ghost_count), 0.0); + std::vector recv_frac(6 * static_cast(slot.ghost_count), 0.0); if (slot.send_rank == rank_ && slot.recv_rank == rank_) { recv_frac = send_frac; @@ -635,12 +678,10 @@ void DomainDecomposition::update_ghost_atom_positions(const std::vector(i)]; ghost.frac.set(recv_frac[3 * i], recv_frac[3 * i + 1], recv_frac[3 * i + 2]); - const std::array& image_shift = slot.send_rank == rank_ && slot.recv_rank == rank_ - ? slot.image_shift - : slot.recv_image_shift; - const ModuleBase::Vector3 image_frac(ghost.frac.x + image_shift[0], - ghost.frac.y + image_shift[1], - ghost.frac.z + image_shift[2]); + const ModuleBase::Vector3 image_frac( + recv_frac[3 * static_cast(slot.ghost_count) + 3 * i], + recv_frac[3 * static_cast(slot.ghost_count) + 3 * i + 1], + recv_frac[3 * static_cast(slot.ghost_count) + 3 * i + 2]); ghost.cart = image_frac * latvec_; ghost.force.set(0.0, 0.0, 0.0); } diff --git a/source/source_cell/module_neighlist/domain_decomposition.h b/source/source_cell/module_neighlist/domain_decomposition.h index edbaddeda90..cf5aa7ec81d 100644 --- a/source/source_cell/module_neighlist/domain_decomposition.h +++ b/source/source_cell/module_neighlist/domain_decomposition.h @@ -123,6 +123,8 @@ class DomainDecomposition bool atom_overlaps_target_halo(const LocalAtom& atom, const std::array& target_coords, const std::array& image_shift) const; + std::array image_shift_for_update(const LocalAtom& atom, + const GhostExchangeSlot& slot) const; int neighbor_layer(int dim) const; void build_ghost_exchange_slots(std::vector& slots) const; PackedAtom pack_atom(const LocalAtom& atom, const std::array& image_shift) const; diff --git a/source/source_cell/module_neighlist/test/mdcell_migrate_mpi_test.cpp b/source/source_cell/module_neighlist/test/mdcell_migrate_mpi_test.cpp index 778a411909a..f8674e143cd 100644 --- a/source/source_cell/module_neighlist/test/mdcell_migrate_mpi_test.cpp +++ b/source/source_cell/module_neighlist/test/mdcell_migrate_mpi_test.cpp @@ -208,6 +208,64 @@ TEST(MdCellMigrateMpiTest, SkinUpdatesFixedGhostLayoutBeforeRebuild) } } +TEST(MdCellMigrateMpiTest, SkinUpdatesPeriodicGhostImageWithoutRebuild) +{ + int rank = 0; + int size = 1; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + ASSERT_EQ(size, 2); + + const ModuleBase::Matrix3 latvec = make_lattice(); + const ModuleBase::Vector3 frac(rank == 0 ? 0.49 : 0.99, 0.2, 0.2); + std::vector owned_atoms(1, LocalAtom(frac, + frac, + ModuleBase::Vector3(0.0, 0.0, 0.0), + ModuleBase::Vector3(0.0, 0.0, 0.0), + ModuleBase::Vector3(1, 1, 1), + 1.0, + 0, + rank, + rank)); + MDCell mdcell; + mdcell.initialize_from_owned_atoms(latvec, + latvec.Inverse(), + 1.0, + 1.0, + 2, + owned_atoms, + std::vector(1, "X"), + std::vector(1, 1.0), + std::vector(1, 2), + 0.2, + ModuleBase::world_comm_domain()); + mdcell.initialize_neighbors(0.1); + mdcell.prepare_neighbors(); + + const NeighborSearch* search = &mdcell.neighbor_search(); + if (rank == 1) + { + mdcell.mutable_owned_atoms()[0].frac.x = 0.01; + mdcell.mutable_owned_atoms()[0].cart = mdcell.mutable_owned_atoms()[0].frac * latvec; + } + mdcell.prepare_neighbors(); + EXPECT_EQ(&mdcell.neighbor_search(), search); + + if (rank == 0) + { + bool found_wrapped_ghost = false; + for (const LocalAtom& ghost : mdcell.ghost_atoms()) + { + if (ghost.owner_rank == 1) + { + EXPECT_NEAR(ghost.cart.x, 0.01, 1.0e-12); + found_wrapped_ghost = true; + } + } + EXPECT_TRUE(found_wrapped_ghost); + } +} + int main(int argc, char** argv) { MPI_Init(&argc, &argv); From bcca95fd16ad846689d9d5d2e055d489e0129db1 Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Fri, 9 Oct 2026 19:34:59 +0800 Subject: [PATCH 2/5] test: adapt periodic ghost regression to merged MD API --- .../test/test_domain_decomposition.cpp | 31 ++++++++++ .../test/test_mdcell_migrate_mpi.cpp | 58 ------------------- 2 files changed, 31 insertions(+), 58 deletions(-) diff --git a/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp b/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp index 85423f8f452..47f6c1cf130 100644 --- a/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp +++ b/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp @@ -234,6 +234,37 @@ TEST_F(DomainDecompositionTest, CrossingHaloEdgePreservesCachedGhostSlots) } } +TEST_F(DomainDecompositionTest, PeriodicGhostImageUpdatesWithoutRebuild) +{ + ASSERT_EQ(domain.size(), 2); + decomp.prepare_neighbors(cell); + const NeighborSearch* search = &cell.neighbor_search(); + for (LocalAtom& atom : cell.owned_atoms()) + { + if (atom.type_index == 1) + { + atom.frac.x = 0.01; + atom.cart = atom.frac * cell.latvec(); + } + } + decomp.prepare_neighbors(cell); + EXPECT_EQ(&cell.neighbor_search(), search); + + if (domain.rank() == 0) + { + bool found_wrapped_ghost = false; + for (const LocalAtom& ghost : cell.ghost_atoms()) + { + if (ghost.owner_rank == 1 && ghost.type_index == 1) + { + EXPECT_NEAR(ghost.cart.x, 0.01, 1.0e-12); + found_wrapped_ghost = true; + } + } + EXPECT_TRUE(found_wrapped_ghost); + } +} + TEST_F(DomainDecompositionTest, SkewCellMultipleImagesMatchBruteForce) { ModuleBase::Matrix3 lattice = cell.latvec(); diff --git a/source/source_cell/module_neighlist/test/test_mdcell_migrate_mpi.cpp b/source/source_cell/module_neighlist/test/test_mdcell_migrate_mpi.cpp index 69fb4144f40..865ef7f8254 100644 --- a/source/source_cell/module_neighlist/test/test_mdcell_migrate_mpi.cpp +++ b/source/source_cell/module_neighlist/test/test_mdcell_migrate_mpi.cpp @@ -218,64 +218,6 @@ TEST(MdCellMigrateMpiTest, SkinUpdatesFixedGhostLayoutBeforeRebuild) } } -TEST(MdCellMigrateMpiTest, SkinUpdatesPeriodicGhostImageWithoutRebuild) -{ - int rank = 0; - int size = 1; - MPI_Comm_rank(MPI_COMM_WORLD, &rank); - MPI_Comm_size(MPI_COMM_WORLD, &size); - ASSERT_EQ(size, 2); - - const ModuleBase::Matrix3 latvec = make_lattice(); - const ModuleBase::Vector3 frac(rank == 0 ? 0.49 : 0.99, 0.2, 0.2); - std::vector owned_atoms(1, LocalAtom(frac, - frac, - ModuleBase::Vector3(0.0, 0.0, 0.0), - ModuleBase::Vector3(0.0, 0.0, 0.0), - ModuleBase::Vector3(1, 1, 1), - 1.0, - 0, - rank, - rank)); - MDCell mdcell; - mdcell.initialize_from_owned_atoms(latvec, - latvec.Inverse(), - 1.0, - 1.0, - 2, - owned_atoms, - std::vector(1, "X"), - std::vector(1, 1.0), - std::vector(1, 2), - 0.2, - ModuleBase::world_comm_domain()); - mdcell.initialize_neighbors(0.1); - mdcell.prepare_neighbors(); - - const NeighborSearch* search = &mdcell.neighbor_search(); - if (rank == 1) - { - mdcell.mutable_owned_atoms()[0].frac.x = 0.01; - mdcell.mutable_owned_atoms()[0].cart = mdcell.mutable_owned_atoms()[0].frac * latvec; - } - mdcell.prepare_neighbors(); - EXPECT_EQ(&mdcell.neighbor_search(), search); - - if (rank == 0) - { - bool found_wrapped_ghost = false; - for (const LocalAtom& ghost : mdcell.ghost_atoms()) - { - if (ghost.owner_rank == 1) - { - EXPECT_NEAR(ghost.cart.x, 0.01, 1.0e-12); - found_wrapped_ghost = true; - } - } - EXPECT_TRUE(found_wrapped_ghost); - } -} - int main(int argc, char** argv) { MPI_Init(&argc, &argv); From f17ced331698e5790374234cc7d4e5c00718d562 Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Fri, 9 Oct 2026 19:54:43 +0800 Subject: [PATCH 3/5] fix: track periodic image changes across skin updates --- source/source_cell/domain_decomposition.cpp | 34 ++++++++----------- source/source_cell/domain_decomposition.h | 1 + .../test/test_domain_decomposition.cpp | 15 ++++++-- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/source/source_cell/domain_decomposition.cpp b/source/source_cell/domain_decomposition.cpp index 7d59d8add84..69a0ad732ab 100644 --- a/source/source_cell/domain_decomposition.cpp +++ b/source/source_cell/domain_decomposition.cpp @@ -325,7 +325,9 @@ void DomainDecomposition::update_ghost_atom_positions(MDCell& cell) for (std::size_t i = 0; i < slot.send_atom_indices.size(); ++i) { const LocalAtom& atom = owned_atoms[static_cast(slot.send_atom_indices[i])]; - const std::array image_shift = image_shift_for_update(atom, slot); + const ModuleBase::Vector3& reference_frac = + cell.neighbor_reference_frac_[static_cast(slot.send_atom_indices[i])]; + const std::array image_shift = image_shift_for_update(atom, reference_frac, slot); send_frac[3 * i] = atom.frac.x; send_frac[3 * i + 1] = atom.frac.y; send_frac[3 * i + 2] = atom.frac.z; @@ -781,37 +783,29 @@ void DomainDecomposition::target_for_offset(const std::array& offset, std::array DomainDecomposition::image_shift_for_update( const LocalAtom& atom, + const ModuleBase::Vector3& reference_frac, const GhostExchangeSlot& slot) const { std::array image_shift = slot.image_shift; const double frac[3] = {atom.frac.x, atom.frac.y, atom.frac.z}; + const double reference[3] = {reference_frac.x, reference_frac.y, reference_frac.z}; for (int idim = 0; idim < 3; ++idim) { - // For a one-domain periodic direction, retain the image side of the - // slot so a wrapped atom does not become a duplicate of itself. - if (dims_[idim] == 1 && slot.offset[idim] != 0) + if (slot.offset[idim] == 0) { - if (slot.offset[idim] > 0) - { - image_shift[idim] = frac[idim] < margin_[idim] ? 1 : -1; - } - else - { - image_shift[idim] = frac[idim] > 1.0 - margin_[idim] ? -1 : 1; - } continue; } - const double lo = static_cast(slot.target_coords[idim]) / dims_[idim]; - const double hi = static_cast(slot.target_coords[idim] + 1) / dims_[idim]; - while (frac[idim] + image_shift[idim] < lo - margin_[idim]) + const int crossing = static_cast(std::nearbyint(frac[idim] - reference[idim])); + if (crossing == 0 || image_shift[idim] == 0) { - ++image_shift[idim]; - } - while (frac[idim] + image_shift[idim] >= hi + margin_[idim]) - { - --image_shift[idim]; + continue; } + + // A positive wrapped displacement changes the image index in the + // opposite direction for a positive offset, and vice versa. This + // preserves the same physical image continuously across a boundary. + image_shift[idim] -= slot.offset[idim] > 0 ? crossing : -crossing; } return image_shift; } diff --git a/source/source_cell/domain_decomposition.h b/source/source_cell/domain_decomposition.h index 4a546a716c4..f2d8d881c7a 100644 --- a/source/source_cell/domain_decomposition.h +++ b/source/source_cell/domain_decomposition.h @@ -111,6 +111,7 @@ class DomainDecomposition std::array& target_coords, std::array& image_shift) const; std::array image_shift_for_update(const LocalAtom& atom, + const ModuleBase::Vector3& reference_frac, const GhostExchangeSlot& slot) const; void build_ghost_exchange_slots(std::vector& slots) const; PackedAtom pack_atom(const LocalAtom& atom, const std::array& image_shift) const; diff --git a/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp b/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp index 47f6c1cf130..a2d5cf8c1fe 100644 --- a/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp +++ b/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp @@ -236,7 +236,18 @@ TEST_F(DomainDecompositionTest, CrossingHaloEdgePreservesCachedGhostSlots) TEST_F(DomainDecompositionTest, PeriodicGhostImageUpdatesWithoutRebuild) { - ASSERT_EQ(domain.size(), 2); + if (domain.size() != 2) + { + return; + } + for (LocalAtom& atom : cell.owned_atoms()) + { + if (atom.type_index == 1) + { + atom.frac.x = 0.99; + atom.cart = atom.frac * cell.latvec(); + } + } decomp.prepare_neighbors(cell); const NeighborSearch* search = &cell.neighbor_search(); for (LocalAtom& atom : cell.owned_atoms()) @@ -257,7 +268,7 @@ TEST_F(DomainDecompositionTest, PeriodicGhostImageUpdatesWithoutRebuild) { if (ghost.owner_rank == 1 && ghost.type_index == 1) { - EXPECT_NEAR(ghost.cart.x, 0.01, 1.0e-12); + EXPECT_NEAR(ghost.cart.x, 0.04, 1.0e-12); found_wrapped_ghost = true; } } From 5d40945a50cc5de88db475b3e1bd1c7211ed03ce Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Fri, 9 Oct 2026 20:13:37 +0800 Subject: [PATCH 4/5] fix: rebuild neighbors when atoms cross periodic boundaries --- source/source_cell/domain_decomposition.cpp | 38 ++++++++++---- source/source_cell/domain_decomposition.h | 5 +- .../test/test_domain_decomposition.cpp | 50 ++++++++++++++++++- 3 files changed, 80 insertions(+), 13 deletions(-) diff --git a/source/source_cell/domain_decomposition.cpp b/source/source_cell/domain_decomposition.cpp index 69a0ad732ab..b48a75dd9a6 100644 --- a/source/source_cell/domain_decomposition.cpp +++ b/source/source_cell/domain_decomposition.cpp @@ -264,6 +264,16 @@ void DomainDecomposition::exchange_ghost_atoms(MDCell& cell) { GhostExchangeSlot& slot = slots[islot]; const std::vector& send_atoms = send_buffers[islot]; + slot.previous_fracs.clear(); + slot.dynamic_image_shifts.clear(); + slot.previous_fracs.reserve(slot.send_atom_indices.size()); + slot.dynamic_image_shifts.reserve(slot.send_atom_indices.size()); + for (const int index : slot.send_atom_indices) + { + const LocalAtom& atom = owned_atoms[static_cast(index)]; + slot.previous_fracs.push_back({{atom.frac.x, atom.frac.y, atom.frac.z}}); + slot.dynamic_image_shifts.push_back(slot.image_shift); + } slot.ghost_begin = ghost_atoms.size(); if (send_atoms.size() > static_cast(std::numeric_limits::max())) @@ -318,16 +328,17 @@ void DomainDecomposition::update_ghost_atom_positions(MDCell& cell) std::vector& ghost_atoms = cell.ghost_atoms_; for (std::size_t islot = 0; islot < ghost_slots_.size(); ++islot) { - const GhostExchangeSlot& slot = ghost_slots_[islot]; + GhostExchangeSlot& slot = ghost_slots_[islot]; // Keep wrapped fractional coordinates for metadata and send the // dynamically selected image coordinates in the same message. std::vector send_frac(6 * slot.send_atom_indices.size(), 0.0); for (std::size_t i = 0; i < slot.send_atom_indices.size(); ++i) { const LocalAtom& atom = owned_atoms[static_cast(slot.send_atom_indices[i])]; - const ModuleBase::Vector3& reference_frac = - cell.neighbor_reference_frac_[static_cast(slot.send_atom_indices[i])]; - const std::array image_shift = image_shift_for_update(atom, reference_frac, slot); + const std::array image_shift = image_shift_for_update( + atom, slot.previous_fracs[i], slot.dynamic_image_shifts[i], slot); + slot.previous_fracs[i] = {{atom.frac.x, atom.frac.y, atom.frac.z}}; + slot.dynamic_image_shifts[i] = image_shift; send_frac[3 * i] = atom.frac.x; send_frac[3 * i + 1] = atom.frac.y; send_frac[3 * i + 2] = atom.frac.z; @@ -476,18 +487,25 @@ void DomainDecomposition::prepare_neighbors(MDCell& cell) bool rebuild = !cell.neighbor_layout_valid_ || cell.neighbor_reference_frac_.size() != cell.owned_atoms_.size(); rebuild = domain.max(rebuild || !ghost_layout_valid_ ? 1 : 0) != 0; double local_max_displacement = 0.0; + bool local_periodic_crossing = false; if (!rebuild) { for (std::size_t i = 0; i < cell.owned_atoms_.size(); ++i) { ModuleBase::Vector3 delta = cell.owned_atoms_[i].frac - cell.neighbor_reference_frac_[i]; + if (std::nearbyint(delta.x) != 0.0 || std::nearbyint(delta.y) != 0.0 + || std::nearbyint(delta.z) != 0.0) + { + local_periodic_crossing = true; + } delta.x -= std::nearbyint(delta.x); delta.y -= std::nearbyint(delta.y); delta.z -= std::nearbyint(delta.z); local_max_displacement = std::max(local_max_displacement, (delta * cell.latvec_).norm() * cell.lat0_); } local_max_displacement = domain.max(local_max_displacement); - rebuild = local_max_displacement >= cell.skin_ * 0.5; + rebuild = local_max_displacement >= cell.skin_ * 0.5 + || domain.max(local_periodic_crossing ? 1 : 0) != 0; } if (rebuild) @@ -783,12 +801,12 @@ void DomainDecomposition::target_for_offset(const std::array& offset, std::array DomainDecomposition::image_shift_for_update( const LocalAtom& atom, - const ModuleBase::Vector3& reference_frac, + const std::array& previous_frac, + const std::array& previous_image_shift, const GhostExchangeSlot& slot) const { - std::array image_shift = slot.image_shift; + std::array image_shift = previous_image_shift; const double frac[3] = {atom.frac.x, atom.frac.y, atom.frac.z}; - const double reference[3] = {reference_frac.x, reference_frac.y, reference_frac.z}; for (int idim = 0; idim < 3; ++idim) { if (slot.offset[idim] == 0) @@ -796,8 +814,8 @@ std::array DomainDecomposition::image_shift_for_update( continue; } - const int crossing = static_cast(std::nearbyint(frac[idim] - reference[idim])); - if (crossing == 0 || image_shift[idim] == 0) + const int crossing = static_cast(std::nearbyint(frac[idim] - previous_frac[idim])); + if (crossing == 0) { continue; } diff --git a/source/source_cell/domain_decomposition.h b/source/source_cell/domain_decomposition.h index f2d8d881c7a..8c503525bad 100644 --- a/source/source_cell/domain_decomposition.h +++ b/source/source_cell/domain_decomposition.h @@ -76,6 +76,8 @@ class DomainDecomposition int send_rank; int recv_rank; std::vector send_atom_indices; + std::vector> previous_fracs; + std::vector> dynamic_image_shifts; std::size_t ghost_begin; int ghost_count; }; @@ -111,7 +113,8 @@ class DomainDecomposition std::array& target_coords, std::array& image_shift) const; std::array image_shift_for_update(const LocalAtom& atom, - const ModuleBase::Vector3& reference_frac, + const std::array& previous_frac, + const std::array& previous_image_shift, const GhostExchangeSlot& slot) const; void build_ghost_exchange_slots(std::vector& slots) const; PackedAtom pack_atom(const LocalAtom& atom, const std::array& image_shift) const; diff --git a/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp b/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp index a2d5cf8c1fe..26ee1a0579a 100644 --- a/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp +++ b/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp @@ -234,7 +234,7 @@ TEST_F(DomainDecompositionTest, CrossingHaloEdgePreservesCachedGhostSlots) } } -TEST_F(DomainDecompositionTest, PeriodicGhostImageUpdatesWithoutRebuild) +TEST_F(DomainDecompositionTest, PeriodicGhostImageRebuildsOnBoundaryCrossing) { if (domain.size() != 2) { @@ -259,7 +259,7 @@ TEST_F(DomainDecompositionTest, PeriodicGhostImageUpdatesWithoutRebuild) } } decomp.prepare_neighbors(cell); - EXPECT_EQ(&cell.neighbor_search(), search); + EXPECT_NE(&cell.neighbor_search(), search); if (domain.rank() == 0) { @@ -276,6 +276,52 @@ TEST_F(DomainDecompositionTest, PeriodicGhostImageUpdatesWithoutRebuild) } } +TEST_F(DomainDecompositionTest, PeriodicGhostImageUpdatesAcrossRepeatedCrossings) +{ + if (domain.size() != 2) + { + return; + } + for (LocalAtom& atom : cell.owned_atoms()) + { + if (atom.type_index == 1) + { + atom.frac.x = 0.99; + atom.cart = atom.frac * cell.latvec(); + } + } + decomp.prepare_neighbors(cell); + + const double positions[4] = {0.01, 0.99, 0.01, 0.99}; + for (double position : positions) + { + for (LocalAtom& atom : cell.owned_atoms()) + { + if (atom.type_index == 1) + { + atom.frac.x = position; + atom.cart = atom.frac * cell.latvec(); + } + } + decomp.prepare_neighbors(cell); + + if (domain.rank() == 0) + { + bool found_wrapped_ghost = false; + for (const LocalAtom& ghost : cell.ghost_atoms()) + { + if (ghost.owner_rank == 1 && ghost.type_index == 1) + { + const double expected_cart_x = position < 0.5 ? position * 4.0 : position * 4.0 - 4.0; + EXPECT_NEAR(ghost.cart.x, expected_cart_x, 1.0e-12); + found_wrapped_ghost = true; + } + } + EXPECT_TRUE(found_wrapped_ghost); + } + } +} + TEST_F(DomainDecompositionTest, SkewCellMultipleImagesMatchBruteForce) { ModuleBase::Matrix3 lattice = cell.latvec(); From 8622a0f26c30fd47fbdc2cff7dbc47c391bb5c96 Mon Sep 17 00:00:00 2001 From: Fei Yang <2501213217@stu.pku.edu.cn> Date: Fri, 9 Oct 2026 20:15:23 +0800 Subject: [PATCH 5/5] test: cover periodic wrap rebuilds and repeated crossings --- .../test/test_domain_decomposition.cpp | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp b/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp index 26ee1a0579a..e7ba6e1ec49 100644 --- a/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp +++ b/source/source_cell/module_neighlist/test/test_domain_decomposition.cpp @@ -261,24 +261,22 @@ TEST_F(DomainDecompositionTest, PeriodicGhostImageRebuildsOnBoundaryCrossing) decomp.prepare_neighbors(cell); EXPECT_NE(&cell.neighbor_search(), search); - if (domain.rank() == 0) + long long local_type_one = 0; + for (const LocalAtom& atom : cell.owned_atoms()) { - bool found_wrapped_ghost = false; - for (const LocalAtom& ghost : cell.ghost_atoms()) + if (atom.type_index == 1) { - if (ghost.owner_rank == 1 && ghost.type_index == 1) - { - EXPECT_NEAR(ghost.cart.x, 0.04, 1.0e-12); - found_wrapped_ghost = true; - } + EXPECT_NEAR(atom.cart.x, 0.04, 1.0e-12); + ++local_type_one; } - EXPECT_TRUE(found_wrapped_ghost); } + Parallel_Reduce::reduce_all(local_type_one); + EXPECT_EQ(local_type_one, 1); } TEST_F(DomainDecompositionTest, PeriodicGhostImageUpdatesAcrossRepeatedCrossings) { - if (domain.size() != 2) + if (domain.size() != 1) { return; } @@ -291,6 +289,7 @@ TEST_F(DomainDecompositionTest, PeriodicGhostImageUpdatesAcrossRepeatedCrossings } } decomp.prepare_neighbors(cell); + const NeighborSearch* previous_search = &cell.neighbor_search(); const double positions[4] = {0.01, 0.99, 0.01, 0.99}; for (double position : positions) @@ -304,21 +303,19 @@ TEST_F(DomainDecompositionTest, PeriodicGhostImageUpdatesAcrossRepeatedCrossings } } decomp.prepare_neighbors(cell); + EXPECT_NE(&cell.neighbor_search(), previous_search); + previous_search = &cell.neighbor_search(); - if (domain.rank() == 0) + bool found_owned_atom = false; + for (const LocalAtom& atom : cell.owned_atoms()) { - bool found_wrapped_ghost = false; - for (const LocalAtom& ghost : cell.ghost_atoms()) + if (atom.type_index == 1) { - if (ghost.owner_rank == 1 && ghost.type_index == 1) - { - const double expected_cart_x = position < 0.5 ? position * 4.0 : position * 4.0 - 4.0; - EXPECT_NEAR(ghost.cart.x, expected_cart_x, 1.0e-12); - found_wrapped_ghost = true; - } + EXPECT_NEAR(atom.cart.x, position * 4.0, 1.0e-12); + found_owned_atom = true; } - EXPECT_TRUE(found_wrapped_ghost); } + EXPECT_TRUE(found_owned_atom); } }