Skip to content

Parallelize periodic RVE boundary conditions - #2102

Open
m-frey wants to merge 1 commit into
4C-multiphysics:mainfrom
m-frey:dev-pbc-parallel
Open

Parallelize periodic RVE boundary conditions#2102
m-frey wants to merge 1 commit into
4C-multiphysics:mainfrom
m-frey:dev-pbc-parallel

Conversation

@m-frey

@m-frey m-frey commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Description and Context

The periodic RVE boundary conditions in the constraint framework were
serial-only and aborted with a FOUR_C_THROW on more than one rank. This
enables them in parallel.

Changes:

  • Match periodic node pairs with a distributed ArborX search
    (global_collision_search) instead of a rank-local BVH query.
  • Ghost the matched partner and corner nodes onto the owning rank so all
    four nodes of a constraint are locally available.
  • Assemble each constraint row on a single owner; derive Q_dL
    and the residual from a distributed mat-vec, so no off-processor
    assembly is needed.

Related Issues and Pull Requests

@m-frey
m-frey requested review from isteinbrecher and mayrmt June 29, 2026 16:09
@m-frey m-frey self-assigned this Jun 29, 2026
Copilot AI review requested due to automatic review settings June 29, 2026 16:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

if (is_parallel &&
rve_ref_type_ == Constraints::MultiPoint::RveReferenceDeformationDefinition::manual)
{
FOUR_C_THROW("Manual RVE reference points are not implemented in parallel.");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be use FOUR_C_ASSERT_ALWAYS here. Same below.

//! Return the Penalty-Parameter
double& get_penalty_parameter_ptr() { return penalty_parameter_; }

//! Set the constraint rows owned by this rank

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these actually row IDs in a matrix or the global IDs of DOFs, that are subject to a constraint?

Comment on lines +48 to +49
//! Suspend floating point exception trapping while in scope. ArborX may raise benign fp
//! exceptions on ranks whose local search input is empty.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//! Suspend floating point exception trapping while in scope. ArborX may raise benign fp
//! exceptions on ranks whose local search input is empty.
//! Suspend floating point exception trapping while in scope. ArborX may raise benign floating point
//! exceptions on ranks whose local search input is empty.

Comment on lines 81 to +82
discret_ptr_ = disc_ptr;
writable_discret_ = std::move(disc_ptr);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Is discret_ptr_ still a valid point with actual data at this point?
  2. Is it still used anywhere?

Comment on lines +967 to +972
// ghosting must not change the dof row map
const Core::LinAlg::Map& dof_row_map_after = *writable_discret_->dof_row_map();
const std::vector<int> dof_gids_after(dof_row_map_after.my_global_elements(),
dof_row_map_after.my_global_elements() + dof_row_map_after.num_my_elements());
if (dof_gids_before != dof_gids_after)
FOUR_C_THROW("Ghosting the periodic partner nodes changed the dof row map.");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks like code, that should be run in DEBUG mode only.

@bennoschoenstein bennoschoenstein left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot! Exactly what I need 🗡️

std::map<int, std::vector<int>> positive_partners;
for (const auto& match : matches)
positive_partners[match.gid_predicate].push_back(match.gid_primitive);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about if nodes don't find any partner?

The old code checked nHits == 0 for every '-' node and threw.
Now positive_partners only contains nodes that actually found a partner. A '-' node without a partner (mesh not perfectly periodic, or POINT_TOLERANCE too small) is now skipped - the simulation continues with a missing constraint and gives wrong results without any warning.

FIX:

if (positive_partners.size() != shifted_negative_nodes.size())
        {
          std::string unmatched_gids;
          for (const auto& [minus_gid, bounding_volume] : shifted_negative_nodes)
            if (!positive_partners.contains(minus_gid))
              unmatched_gids += std::to_string(minus_gid) + " ";
          FOUR_C_THROW(
              "Periodic search on the '{}-' boundary found no partner for node(s): {}."
              " Check mesh periodicity or increase POINT_TOLERANCE.",
              axis, unmatched_gids);
        }

Maybe also worth a small test with an intentionally non-periodic mesh?

}
// assemble the rows owned by this rank
for (const auto& [coefficient, row_id, dof_id] : equation_data_)
if (Q_Ld.row_map().my_gid(row_id)) Q_Ld.assemble(coefficient, row_id, dof_id);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if drops rows that are not in the row map. For the periodic constraints this never happens. But for DESIGN POINT COUPLED DOF EQUATION CONDITIONS together with RVE_REFERENCE_POINTS: automatic it does happen.

FIX:

for (const auto& [coefficient, row_id, dof_id] : equation_data_)
{
    FOUR_C_ASSERT_ALWAYS(Q_Ld.row_map().my_gid(row_id),
        "Constraint equation row {} is not in the constraint row map and would "
        "be dropped.",
        row_id);
    Q_Ld.assemble(coefficient, row_id, dof_id);
  }

Core::IO::cout(Core::IO::verbose)
<< "\nNumber of periodic constraint equations on this rank: "
<< constraint_equations_.size() << Core::IO::endl;
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This early return skips the "Ensure that no constraint equation is used twice" block further down, which the old automatic path still reached.

Note: two duplicates can be created on different ranks. Maybe the simplest fix is to not create the duplicate in the first place, e.g. skip the known corner nodes in the '-' node loop for all axes but one

for (const auto& [minus_gid, partners] : positive_partners)
{
if (partners.size() != 1)
FOUR_C_THROW(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If only one rank throws, the others can end up waiting instead of aborting

Core::LinAlg::Map ghosted_node_map(-1, static_cast<int>(ghosted_node_gids.size()),
ghosted_node_gids.data(), 0, writable_discret_->get_comm());
writable_discret_->export_column_nodes(ghosted_node_map);
writable_discret_->fill_complete();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this fill_complete() rebuilds the column maps and re-assigns the dofs. Anything that grabbed column-map data from this discretization before this constructor runs would silently be stale...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants