Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6028624
Add combine call so to_polytopal outputs one domain/rank.
nselliott Feb 26, 2026
1f5d42d
Add vertex-based adjsets to the output domains of to_polygonal
nselliott Mar 17, 2026
adbc821
Fix handling of partial_lo/hi cases for polygonal adjsets
nselliott Apr 3, 2026
952952b
Add generate_boundary
nselliott Apr 24, 2026
f28d732
Namespace cleanup and some logic to handle ranks with empty meshes.
nselliott Apr 28, 2026
9735f29
Add normalize_adjset_groups
nselliott Apr 30, 2026
185ff8e
Modify generate_boundary to handle domains that don't touch boundary
nselliott Apr 30, 2026
cd5c969
Change partial_lo/hi to have a value for each dimension.
nselliott May 15, 2026
1bf8d61
Improve verify function for structured window adjsets
nselliott May 15, 2026
48b3c97
Make partial_lo/hi be consistently stored on the fine-side window
nselliott Jun 1, 2026
0647852
Add documentation for structured adjsets for AMR
nselliott Jun 1, 2026
70cd2eb
Merge branch 'develop' into feature/nselliott/polygonal-adjsets
nselliott Jun 1, 2026
4cc96f7
Add comments for helper functions
nselliott Jun 2, 2026
c055b10
Make rank/level_id optional in structured domain verify; fix
nselliott Jun 3, 2026
204e081
Remove requirement in verify function that both partial_lo and partia…
nselliott Jun 3, 2026
314e0ea
Update t_blueprint_mpi_mesh_polytopal for changes in to_polytopal out…
nselliott Jun 4, 2026
c5b6f4f
Add block rotation to polytopal test
nselliott Jun 5, 2026
2148d6e
Clarify documentation of orientation entry in adjsets
nselliott Jun 5, 2026
78ef7f3
Fix bugs in structured window adjset verify
nselliott Jun 11, 2026
9f52cde
Refactoring of lambdas and some added comments for clarity
nselliott Jun 11, 2026
15d76e3
Refactor of search for coarse/fine adjset vertex ids.
nselliott Jun 11, 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
81 changes: 76 additions & 5 deletions src/docs/sphinx/blueprint_mesh.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1452,7 +1452,12 @@ Adjacency Set Variants

There's a great deal of flexibility in how the adjacency groups of an Adjacency Set can be constructed.
Blueprint Mesh contains detection and transformation functions for the most commonly targeted formats.
The two variants currently supported are **pairwise** and **max-share**.
The two variants currently supported by the core Blueprint adjset utilities are **pairwise** and **max-share**.

Some tools also consume a third, specialized adjset variant used to describe the *interface* between
neighboring structured domains (including coarse/fine neighbors in AMR). This variant is used by
``conduit::blueprint::mpi::mesh::to_polytopal`` when converting structured multi-domain meshes into
polygonal / polyhedral meshes.


Pairwise Adjacency Sets
Expand Down Expand Up @@ -1532,6 +1537,76 @@ The following diagram illustrates a simple **max-share** material set example:
values: [v01]


Structured Neighbor Window Adjacency Sets
************************************************

This adjset variant is intended for structured meshes where each adjacency *group* describes a single
neighboring domain and includes enough information to locate the overlap (in structured index space) on
both the local and neighbor domains. Unlike pairwise/max-share adjsets, these groups do **not** contain
a ``values`` array.

Each group contains:

* ``neighbors``: integer pair of the IDs of the current domain and the neighbor domain ``[domain_id, neighbor_domain_id]``.
* ``rank``: the neighbor domain's MPI rank.
* ``windows``: an object holding two per-domain "window" descriptions, keyed by the zero-padded domain id for each domain (e.g. ``window_000012``).
* ``orientation`` (optional): an integer vector for the orientation in rotated multi-block meshes
- The vector is indexed by the neighbor domain's logical axes in ``[i, j, k]`` order.
- Each entry describes how positive traversal of a neighbor's axis maps onto the
current domain's logical axes.
- Each entry is one of {+/-1, +/-2, +/-3}:
- abs(value) is an integer representation of the axis labels,
(1->i, 2->j, 3->k).
- sign(value) is the direction along that current-domain axis
(+ means aligned, - means reversed).
- For example, in 2D ``orientation: [-2, 1]`` means:
- traversing the neighbor's ``+i`` direction corresponds to traversing the
current domain's ``-j`` direction
- traversing the neighbor's ``+j`` direction corresponds to traversing the
current domain's ``+i`` direction
The corresponding group stored on the neighbor domain would use the inverse
mapping, ``[2, -1]``.

Each window describes the overlap in structured index space for the corresponding domain:

* ``level_id``: the AMR level for that domain.
* ``origin/{i,j,k}``: the starting index of the overlap window.
* ``dims/{i,j,k}``: the extents (number of points) of the overlap window.
* ``ratio/{i,j,k}``: refinement ratio information for the overlap.
* ``partial_lo/{i,j}``, ``partial_hi/{i,j}`` (optional, 2D only): present only on the fine-side window of a coarse/fine adjacency. They count padded fine-side samples at the low and high ends of the window, along the varying dimension(s), used when the overlap is not aligned to an exact refinement multiple. Consumers skip the first ``partial_lo`` samples and the last ``partial_hi`` samples from that fine-side window.

An example for a single domain with one neighbor looks like:

.. code:: yaml

domain_000010:
state:
domain_id: 10
level_id: 1
adjsets:
adjset:
association: vertex
topology: topology
groups:
group_000011:
neighbors: [10, 11]
rank: 3
orientation: [1, 2, 3] # optional
windows:
window_000010: # local side of the interface
level_id: 1
origin: {i: 32, j: 0}
dims: {i: 1, j: 65}
ratio: {i: 2, j: 2}
partial_lo: {j: 0} # optional
partial_hi: {j: 0} # optional
window_000011: # neighbor side of the interface
level_id: 0
origin: {i: 32, j: 0}
dims: {i: 1, j: 33}
ratio: {i: 2, j: 2}


State
++++++++++++++++++++

Expand Down Expand Up @@ -2596,7 +2671,3 @@ them in ``<`` and ``>`` characters. An example expressions entry in the index is

.. Properties and Transforms
.. ---------------------------




137 changes: 136 additions & 1 deletion src/libs/blueprint/conduit_blueprint_mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <cmath>
#include <cstring>
#include <iostream>
#include <iomanip>
#include <limits>
#include <memory>
#include <set>
Expand Down Expand Up @@ -7328,6 +7329,39 @@ mesh::adjset::verify(const Node &adjset,
group_res &= verify_object_field(protocol, chld,
chld_info, "windows");

// Structured neighbor "window" adjset groups are used in
// meshes with structured topologies and are required
// to include neighbors: [domain_id, neighbor_domain_id].
// If rank is provided, it must be an integer.
bool has_neighbor_pair = true;
has_neighbor_pair &= verify_integer_field(protocol, chld, chld_info, "neighbors");
group_res &= has_neighbor_pair;

if(chld.has_child("rank"))
{
group_res &= verify_integer_field(protocol, chld, chld_info, "rank");
}

index_t dom_id = -1;
index_t nbr_id = -1;
if(has_neighbor_pair)
{
const auto nbrs = chld["neighbors"].as_index_t_accessor();
if(nbrs.number_of_elements() != 2)
{
log::error(info, protocol,
"adjset group 'neighbors' must have exactly 2 entries "
"([domain_id, neighbor_domain_id])");
has_neighbor_pair = false;
group_res = false;
}
else
{
dom_id = nbrs[0];
nbr_id = nbrs[1];
}
}

bool windows_res = true;
NodeConstIterator witr = chld["windows"].children();
while(witr.has_next())
Expand All @@ -7349,6 +7383,11 @@ mesh::adjset::verify(const Node &adjset,
wndw_info, "ratio") &&
mesh::logical_dims::verify(wndw["ratio"],
wndw_info["ratio"]);
if(wndw.has_child("level_id"))
{
window_res &= verify_integer_field(protocol, wndw,
wndw_info, "level_id");
}

// verify that dimensions for "origin" and
// "dims" and "ratio" are the same
Expand All @@ -7363,11 +7402,108 @@ mesh::adjset::verify(const Node &adjset,
wndw_info, "ratio", false, window_dim);
}

// verify partial_lo / partial_hi entries independently
// when present. Missing entries are treated as zero by
// structured adjset consumers.
if(window_res && (wndw.has_child("partial_lo") || wndw.has_child("partial_hi")))
{
bool partial_res = true;

const std::string partial_names[2] =
{"partial_lo", "partial_hi"};
for(index_t pi = 0; pi < 2; pi++)
{
const std::string &partial_name = partial_names[pi];
if(!wndw.has_child(partial_name))
{
continue;
}

bool partial_field_res = verify_object_field(protocol,
wndw, wndw_info, partial_name, false, true);
if(partial_field_res)
{
const Node &partial = wndw[partial_name];
NodeConstIterator pitr = partial.children();
while(pitr.has_next())
{
const Node &p = pitr.next();
const std::string dim_name = pitr.name();
bool partial_dim_res = true;

if(!wndw["origin"].has_child(dim_name))
{
log::error(wndw_info[partial_name][dim_name],
protocol,
log::quote(partial_name + "/" + dim_name) +
"does not match a window dimension");
partial_dim_res = false;
}
partial_dim_res &= verify_integer_field(protocol,
partial, wndw_info[partial_name], dim_name);

// If ratio exists for this dimension, enforce
// 0 <= partial_* < ratio.
if(partial_dim_res && wndw.has_path("ratio/" + dim_name))
{
const index_t rv = wndw["ratio"][dim_name].to_index_t();
const index_t pv = p.to_index_t();
if(rv <= 0 || pv < 0 || pv >= rv)
{
log::error(wndw_info[partial_name][dim_name],
protocol,
log::quote(partial_name + "/" + dim_name) +
"is outside the valid range");
partial_dim_res = false;
}
}

log::validation(wndw_info[partial_name][dim_name],
partial_dim_res);
partial_field_res &= partial_dim_res;
}

log::validation(wndw_info[partial_name],
partial_field_res);
}

partial_res &= partial_field_res;
}
window_res &= partial_res;
}

log::validation(wndw_info,window_res);
windows_res &= window_res;
}

// Enforce that window group contains exactly the two expected
// window entries: window_<domain_id> and window_<neighbor_id>.
if(has_neighbor_pair && windows_res)
{
const index_t num_windows = chld["windows"].number_of_children();
if(num_windows != 2)
{
log::error(info, protocol,
"adjset group 'windows' must contain exactly 2 windows");
windows_res = false;
}
else
{
const std::string dom_win = bputils::gen_default_name("window", dom_id);
const std::string nbr_win = bputils::gen_default_name("window", nbr_id);
if(!chld["windows"].has_child(dom_win) ||
!chld["windows"].has_child(nbr_win))
{
log::error(info, protocol,
"adjset group 'windows' must contain '" +
dom_win + "' and '" + nbr_win + "'");
windows_res = false;
}
}
}

log::validation(chld_info["windows"],windows_res);
group_res &= windows_res;
res &= windows_res;

if(chld.has_child("orientation"))
Expand Down Expand Up @@ -9633,4 +9769,3 @@ void mesh::rename(const conduit::Node &n_options,
//-----------------------------------------------------------------------------
// -- end conduit:: --
//-----------------------------------------------------------------------------

Loading
Loading