Skip to content

shore.balance

Python API for the multi-way equal-partition split planner. The CLI wrapper shore balance is a thin adapter around plan_balance.

The algorithm, the multigrid floor, and the design rationale are documented at shore balance and Design decisions.

Public exports

NameRole
plan_balanceThe planner. Reads a .grd (optionally .adjacency.json + wired blocks) and returns a BalancePlan.
BalancePlanDataclass holding the planned splits + before/after summaries. to_toml() renders a shore split config; to_bsplit_par() renders a bsplit.par.

plan_balance

python
def plan_balance(
    grd_path: str | Path,
    *,
    np: int,
    blocks_per_rank: float = 1.0,
    ngr: int = 1,
    min_coarse_cells: int = 8,
    tolerance: float = 0.05,
    adjacency_path: str | Path | None = None,
    blocks: list | None = None,
) -> BalancePlan

Partition every block into roughly equal, roughly cubic chunks toward np * blocks_per_rank, keeping every chunk axis above the multigrid floor.

ParameterDescription
grd_pathPath to the input .grd (block ordering + cell counts).
npTotal MPI rank count (>= 1).
blocks_per_rankTarget average blocks per rank (default 1.0). Higher → finer granularity / better balance, more blocks.
ngrMultigrid levels the mesh must support (default 1). Chunk axes are kept divisible by 2^(ngr-1).
min_coarse_cellsMinimum cells per axis on the coarsest (level-ngr) grid (default 8). Chunk axes are kept >= min_coarse_cells * 2^(ngr-1) fine cells.
toleranceTarget fractional imbalance (max - min) / mean for the converged flag (default 0.05). Never causes a failure.
adjacency_pathOptional .adjacency.json sidecar — used for labels / block-count validation.
blocksOptional list of wired HexBlocks. When given, the planner floor-checks against seam-propagated cuts (via shore.split.propagate_cuts) so the plan matches the split output. Without it, the floor is checked per block in isolation, and the plan uses synthetic block_<n> labels (required for to_bsplit_par).

Returns a BalancePlan. Best-effort: when the floor prevents reaching tolerance, it warns (UserWarning) and returns the best plan with converged=False.

Raises

  • ParameterErrornp < 1, ngr < 1, min_coarse_cells < 1, tolerance <= 0, or blocks_per_rank <= 0.
  • FileNotFoundError, ValueError — forwarded from read_grd_metadata and the adjacency reader.

BalancePlan

python
@dataclass
class BalancePlan:
    splits:           list[tuple[str, str, int]]  # (label, axis, vertex_index)
    iterations:       int
    converged:        bool
    initial_summary:  BalanceSummary
    final_summary:    BalanceSummary
    tolerance:        float
AttributeDescription
splitsCuts in decision order: (original_block_label, axis, original_block_vertex_index). Vertex indices are relative to the original block, not any intermediate chunk — this is what shore split's TOML schema expects.
iterationsNumber of split iterations the planner actually ran.
convergedTrue iff the final imbalance is within tolerance.
initial_summaryBalanceSummary before any splits.
final_summaryBalanceSummary after the last split.
toleranceThe tolerance the planner targeted.

BalancePlan.to_toml

python
def to_toml(self) -> str

Render the plan as a shore split config TOML (version = 1, kind = "split", one [[splits]] per (label, axis) pair with a sorted at = [...] list). Same schema as the example in the shore split reference.

BalancePlan.to_bsplit_par

python
def to_bsplit_par(
    self, *,
    ngr: int = 1,
    sol_root: str = "sol",
    grd_root: str = "cc",
    icc_root: str = "cc",
    varn: int = 0,
    varo: int = 0,
    debug: int = 0,
) -> str

Render the plan as a bsplit.par for the external bsplit tool, which splits the framed overset outputs after an overset run (the post-overset analogue of to_toml + shore split). See After-overset splitting.

Requires a grd-only plan (no adjacency_path/blocks): its synthetic block_<n> labels carry the 1-based grd block order, which is exactly bsplit's block number. Raises ParameterError if any split label is not block_<n>.

Example

python
from shore.balance import plan_balance

plan = plan_balance(
    "wall.grd",
    np=16,
    tolerance=0.05,
    adjacency_path="wall.adjacency.json",
)

print(f"converged: {plan.converged}")
print(f"initial: {plan.initial_summary.imbalance * 100:.1f}%")
print(f"final:   {plan.final_summary.imbalance * 100:.1f}%")
print(f"splits:  {len(plan.splits)} cuts across "
      f"{len({s[0] for s in plan.splits})} blocks")

with open("wall.splits.toml", "w") as f:
    f.write(plan.to_toml())

See also

Released under the MIT License.