Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
e84da5d
Add equidistribution marking (c++)
schnellerhase Apr 24, 2026
6945e6f
Pull out common routine mark_threshold
schnellerhase Apr 27, 2026
c4a2bdb
Add equidistribution marking (py)
schnellerhase Apr 27, 2026
a5ff0dd
Merge branch 'main' into schnellerhase/equidistribution-marking
schnellerhase Apr 30, 2026
e997738
Use inner prodcut
schnellerhase Apr 30, 2026
b9831ba
Improve docstring
schnellerhase Apr 30, 2026
6e9b9c4
use latex
schnellerhase Apr 30, 2026
59af09c
Merge branch 'main' into schnellerhase/equidistribution-marking
schnellerhase May 1, 2026
de0b6a0
Docs: differentiate between eta and marker consistently
schnellerhase May 1, 2026
96ae33e
Merge branch 'main' into schnellerhase/equidistribution-marking
schnellerhase May 4, 2026
4a63e73
Merge branch 'main' into schnellerhase/equidistribution-marking
schnellerhase May 5, 2026
b4690b8
Merge branch 'main' into schnellerhase/equidistribution-marking
schnellerhase May 6, 2026
573e125
Merge branch 'main' into schnellerhase/equidistribution-marking
schnellerhase May 7, 2026
10575df
Merge branch 'main' into schnellerhase/equidistribution-marking
schnellerhase May 7, 2026
77d35fe
Add mark_equidistribution_squared
schnellerhase May 7, 2026
425a642
Fix and python exports
schnellerhase May 7, 2026
63ace3f
Drop code duplication
schnellerhase May 7, 2026
8d2ae29
format
schnellerhase May 7, 2026
c588fb7
Rename eta to indicator
schnellerhase May 7, 2026
f56fae3
format
schnellerhase May 7, 2026
b768685
Apply suggestions from code review
schnellerhase May 7, 2026
3d3be2a
Merge branch 'main' into schnellerhase/equidistribution-marking
schnellerhase May 8, 2026
aa60ca1
Merge branch 'main' into schnellerhase/equidistribution-marking
schnellerhase May 11, 2026
d3b4a9b
Explicit capture
schnellerhase May 11, 2026
73ea789
Improve docs
schnellerhase May 11, 2026
10ced01
Merge branch 'main' into schnellerhase/equidistribution-marking
schnellerhase May 11, 2026
6b2cca2
Apply docstring improvements
schnellerhase May 12, 2026
625957b
Remove walrus
schnellerhase May 12, 2026
d9656db
Pass dtype to random
schnellerhase May 12, 2026
cf0121f
Comm first arg
schnellerhase May 12, 2026
9b48e05
Merge branch 'main' into schnellerhase/equidistribution-marking
schnellerhase May 14, 2026
8dd7c9f
Merge branch 'main' into schnellerhase/equidistribution-marking
schnellerhase May 15, 2026
a65c3c1
WIP - Docstring adjustments
jhale May 19, 2026
08bb596
fixup docs
schnellerhase May 19, 2026
cb56b7a
Merge branch 'main' into schnellerhase/equidistribution-marking
schnellerhase May 19, 2026
6f3733c
Fixup docs and remove potential overflow on summing std::int32_t
jhale May 20, 2026
477c05c
size_t
jhale May 20, 2026
a498c3b
This is optimal unless the expected mark fraction is very small (< 5%).
jhale May 20, 2026
e533aed
Fixes
jhale May 20, 2026
5fd1a96
More fixes
jhale May 20, 2026
5455f00
Fix wrappers nbarg and consistent variable names
jhale May 20, 2026
f17e463
clang-format
jhale May 20, 2026
ddc3eb4
markers -> indicators in tests
jhale May 20, 2026
15e0438
Improve docstring
jhale May 20, 2026
fa679a2
Add Python wrappers for mark_* refinement functions
jhale May 20, 2026
c475ca0
Use Real
jhale May 20, 2026
43b5203
markers -> indicators in mark.cpp test
jhale May 20, 2026
ffc96ab
Make squared_indicators very explicit
jhale May 20, 2026
5dbcea4
Fix up
jhale May 20, 2026
86f9765
Ruff manual fix
jhale May 20, 2026
4936a94
Raw strings
jhale May 20, 2026
254c711
Fix double backslashes in raw string docstrings
jhale May 20, 2026
7c5f5a6
Remove std::pow
jhale May 20, 2026
6caed8d
Add warning to docstring
jhale May 20, 2026
bcc5bcd
Add to Python docstring
jhale May 20, 2026
3b464e1
Also fix up tests to show correct usage.
jhale May 20, 2026
573b8da
Simplify docstrings
jhale May 20, 2026
e5e0564
Revert to count_if
jhale May 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 144 additions & 23 deletions cpp/dolfinx/refinement/mark.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2026 Paul T. Kühner
// Copyright (C) 2026 Paul T. Kühner, Jack S. Hale
//
// This file is part of DOLFINX (https://www.fenicsproject.org)
//
Expand All @@ -8,50 +8,171 @@

#include <algorithm>
#include <cassert>
#include <cmath>
#include <concepts>
#include <cstdint>
#include <limits>
#include <mpi.h>
#include <numeric>
#include <span>
#include <spdlog/spdlog.h>
#include <stdexcept>
#include <vector>

#include "dolfinx/common/MPI.h"

namespace dolfinx::refinement
{

/// @brief Maximum marking of a marker.
namespace impl
Comment thread
jhale marked this conversation as resolved.
{

/// @brief Computes local threshold marking of indicators.
///
/// Helper for other marking routines.
///
/// Returns the indices \f$ i \f$ of the indicators \f$ \eta_i \f$ that satisfy
/// the threshold: \f$ \eta_i > \text{threshold} \f$.
///
/// @param[in] marker Input marker (local) - usually an error indicator per
/// entity
/// @param[in] theta Cut off parameter, 0 < θ < 1
/// @param[in] comm Communicator over which the maximum is computed.
/// @return Indices (local) of marker elements, which satisfy: marker_i ≥ θ
/// max(marker).
/// @param[in] indicators Indicators (local) \f$ \eta_i \f$ -
/// usually an error indicator associated with mesh entity \f$ i \f$.
/// @param[in] threshold Threshold value; indicators greater than this are
/// marked.
/// @return Local indices of marked entities.
template <std::floating_point T>
std::vector<std::int32_t> mark_maximum(std::span<const T> marker, T theta,
MPI_Comm comm)
std::vector<std::int32_t> mark_threshold(std::span<const T> indicators,
T threshold)
{
if ((theta <= 0) || (theta >= 1))
throw std::invalid_argument("Theta needs to fullfill 0 < θ < 1.");

T max = marker.empty() ? std::numeric_limits<T>::lowest()
: std::ranges::max(marker);
MPI_Allreduce(MPI_IN_PLACE, &max, 1, dolfinx::MPI::mpi_t<T>, MPI_MAX, comm);

auto mark = [=](T e) { return e > theta * max; };
auto mark = [threshold](T e) { return e > threshold; };

std::vector<std::int32_t> indices;
indices.reserve(std::ranges::count_if(marker, mark));
indices.reserve(std::ranges::count_if(indicators, mark));

for (std::int32_t i = 0; i < static_cast<std::int32_t>(marker.size()); ++i)
for (std::int32_t i = 0; i < static_cast<std::int32_t>(indicators.size());
++i)
{
if (mark(marker[i]))
if (mark(indicators[i]))
indices.push_back(i);
}

spdlog::info("Marking (max) {} / {} (local) entities.", indices.size(),
marker.size());
return indices;
}

} // namespace impl

/// @brief Computes maximum-based marking of indicators.
///
/// Returns the indices \f$ i \f$ of the indicators \f$ \eta_i \f$ that satisfy
/// the maximum threshold: \f$ \eta_i > \theta \max_j \eta_j \f$.
///
/// @param[in] comm Communicator to compute the maximum over.
/// @param[in] indicators Indicators (local) \f$ \eta_i \f$ -
/// usually an error indicator associated with mesh entity \f$ i \f$.
/// @param[in] theta Parameter, \f$ 0 < \theta < 1 \f$.
/// @return Local indices of marked entities.
template <std::floating_point T>
std::vector<std::int32_t> mark_maximum(MPI_Comm comm,
std::span<const T> indicators, T theta)
{
if ((theta <= 0) || (theta >= 1))
throw std::invalid_argument("theta must fulfill 0 < theta < 1.");

T max = indicators.empty() ? std::numeric_limits<T>::lowest()
: std::ranges::max(indicators);
MPI_Allreduce(MPI_IN_PLACE, &max, 1, dolfinx::MPI::mpi_t<T>, MPI_MAX, comm);

std::vector<std::int32_t> indices
= impl::mark_threshold<T>(indicators, theta * max);

spdlog::info("Marking (maximum) {} / {} (local) entities.", indices.size(),
indicators.size());

return indices;
}

/// @brief Computes equidistribution threshold marking of indicators.
///
/// Returns the indices \f$ i \f$ of the indicators \f$ \eta_i \f$ that satisfy
/// the equidistribution threshold: \f$\eta_i > \theta
/// \frac{||\eta||}{\sqrt{N}} \f$ where \f$ N \f$ is the (global) number of
/// indicators.
///
/// @warning `indicators` must contain owned entities only. Ghost values cause
/// double-counting in the global sum reductions.
///
Comment thread
schnellerhase marked this conversation as resolved.
/// @param[in] comm Communicator over which the global equidistribution
/// threshold is computed.
/// @param[in] indicators Indicators for owned local entities \f$ \eta_i \f$
/// - usually associated with mesh entity \f$ i \f$.
/// @param[in] theta Parameter, \f$ 0 < \theta < 1 \f$.
/// @return Local indices of indicators that satisfy the threshold.
template <std::floating_point T>
std::vector<std::int32_t>
mark_equidistribution(MPI_Comm comm, std::span<const T> indicators, T theta)
{
if ((theta <= 0) || (theta >= 1))
throw std::invalid_argument("theta must fulfill 0 < theta < 1.");

T norm = std::inner_product(indicators.begin(), indicators.end(),
indicators.begin(), T{0});

MPI_Allreduce(MPI_IN_PLACE, &norm, 1, dolfinx::MPI::mpi_t<T>, MPI_SUM, comm);

T sqrt_norm = std::sqrt(norm);

// int64_t gives headroom for global sum across ranks.
std::int64_t count = indicators.size();
MPI_Allreduce(MPI_IN_PLACE, &count, 1, dolfinx::MPI::mpi_t<std::int64_t>,
MPI_SUM, comm);

std::vector<std::int32_t> indices = impl::mark_threshold<T>(
indicators, theta * sqrt_norm / std::sqrt(static_cast<T>(count)));

spdlog::info("Marking (equidistribution) {} / {} (local) entities.",
indices.size(), indicators.size());

return indices;
}

/// @brief Computes equidistribution threshold marking of a squared indicator.
///
/// Returns the indices \f$i\f$ of the squared indicators \f$ \eta_i^2 \f$ that
/// satisfy the equidistribution threshold: \f$ \eta_i^2 > \theta^2
/// \frac{||\eta||^2}{N} \f$ where \f$ N \f$ is the (global) number of
/// indicators.
///
/// @warning `squared_indicators` must contain owned entities only. Ghost
/// values cause double-counting in the global sum reductions.
///
/// @param[in] comm Communicator over which the global equidistribution
/// threshold is computed.
/// @param[in] squared_indicators Input squared indicators for owned local
/// entities \f$ \eta^2_i \f$ - usually associated with mesh entity \f$ i \f$.
/// @param[in] theta Parameter, \f$ 0 < \theta < 1 \f$.
/// @return Local indices of squared indicators that satisfy the threshold.
template <std::floating_point T>
std::vector<std::int32_t>
mark_equidistribution_squared(MPI_Comm comm,
std::span<const T> squared_indicators, T theta)
{
if ((theta <= 0) || (theta >= 1))
throw std::invalid_argument("theta must fulfill 0 < theta < 1.");

T norm = std::accumulate(squared_indicators.begin(), squared_indicators.end(),
T{0});

MPI_Allreduce(MPI_IN_PLACE, &norm, 1, dolfinx::MPI::mpi_t<T>, MPI_SUM, comm);

// int64_t gives headroom for global sum across ranks.
std::int64_t count = squared_indicators.size();
MPI_Allreduce(MPI_IN_PLACE, &count, 1, dolfinx::MPI::mpi_t<std::int64_t>,
MPI_SUM, comm);

std::vector<std::int32_t> indices = impl::mark_threshold<T>(
squared_indicators, theta * theta * norm / static_cast<T>(count));

spdlog::info("Marking (equidistribution) {} of {} local entities.",
indices.size(), squared_indicators.size());

return indices;
}
Expand Down
80 changes: 69 additions & 11 deletions cpp/test/mesh/refinement/mark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,95 @@ using namespace dolfinx::refinement;
TEMPLATE_TEST_CASE("Mark maximum empty", "[refinement][mark][maximum]", double,
float)
{
std::vector<TestType> marker;
auto indices = mark_maximum<TestType>(marker, .5, MPI_COMM_WORLD);
std::vector<TestType> indicators;
auto indices = mark_maximum<TestType>(MPI_COMM_WORLD, indicators, .5);
CHECK(indices.size() == 0);
}

TEMPLATE_TEST_CASE("Mark maximum", "[refinement][mark][maximum]", double, float)
{
MPI_Comm comm = MPI_COMM_WORLD;

std::vector<TestType> marker;
marker.reserve(10);
std::vector<TestType> indicators;
indicators.reserve(10);
for (std::size_t i = 0; i < 10; i++)
marker.push_back(10 * dolfinx::MPI::rank(comm) + i);
indicators.push_back(10 * dolfinx::MPI::rank(comm) + i);

TestType theta = 0.5;
auto indices = mark_maximum<TestType>(marker, theta, comm);
auto indices = mark_maximum<TestType>(comm, indicators, theta);

CHECK(std::ranges::all_of(
indices, [&](auto e)
{ return (0 <= e) && (e <= static_cast<std::int32_t>(marker.size())); }));
indices,
[&](auto e)
{
return (0 <= e) && (e <= static_cast<std::int32_t>(indicators.size()));
}));

TestType max = dolfinx::MPI::size(comm) * 10 - 1;
auto mark = [=](auto e) { return e > theta * max; };

CHECK(std::ranges::count_if(marker, mark)
CHECK(std::ranges::count_if(indicators, mark)
== static_cast<std::int32_t>(indices.size()));

for (std::int32_t i = 0; i < static_cast<std::int32_t>(marker.size()); ++i)
for (std::int32_t i = 0; i < static_cast<std::int32_t>(indicators.size());
++i)
{
bool expect_marked = mark(marker[i]);
bool expect_marked = mark(indicators[i]);
bool marked = std::ranges::find(indices, i) != indices.end();
CHECK(expect_marked == marked);
}
}

TEMPLATE_TEST_CASE("Mark equidistribution empty",
"[refinement][mark][equidistribution]", double, float)
{
std::vector<TestType> indicators;
auto indices
= mark_equidistribution<TestType>(MPI_COMM_WORLD, indicators, .5);
CHECK(indices.size() == 0);
}

TEMPLATE_TEST_CASE("Mark equidistribution",
Comment thread
jhale marked this conversation as resolved.
"[refinement][mark][equidistribution]", double, float)
{
MPI_Comm comm = MPI_COMM_WORLD;

std::vector<TestType> indicators;
indicators.reserve(10);
for (std::size_t i = 0; i < 10; i++)
indicators.push_back(10 * dolfinx::MPI::rank(comm) + i);

TestType theta = 0.5;
auto indices = mark_equidistribution<TestType>(comm, indicators, theta);

CHECK(std::ranges::all_of(
indices,
[&](auto e)
{
return (0 <= e) && (e <= static_cast<std::int32_t>(indicators.size()));
}));

std::int32_t n = dolfinx::MPI::size(comm) * 10 - 1;
std::int32_t count = dolfinx::MPI::size(comm) * 10;
TestType norm = std::sqrt(n * (n + 1) * (2 * n + 1) / 6);

auto mark = [=](auto e) { return e > theta * norm / std::sqrt(count); };

CHECK(std::ranges::count_if(indicators, mark)
== static_cast<std::int32_t>(indices.size()));

for (std::int32_t i = 0; i < static_cast<std::int32_t>(indicators.size());
++i)
{
bool expect_marked = mark(indicators[i]);
bool marked = std::ranges::find(indices, i) != indices.end();
CHECK(expect_marked == marked);
}

// squared input
auto indicators_sq = indicators;
std::ranges::for_each(indicators_sq, [](auto& e) { e = std::pow(e, 2); });

CHECK(std::ranges::equal(indices, mark_equidistribution_squared<TestType>(
comm, indicators_sq, theta)));
}
Loading
Loading