Skip to content

optimize_splitting_mask lake consolidation unbalances the partition #2827

Description

@jdhughes-dev

optimize_splitting_mask() moves every cell of a lake into one model after metis has
balanced the partition, so a balanced partition is returned unbalanced and nothing reports
that it happened.

model_splitter.py:750:

        if laks:
            for lak in laks:
                idx = np.asarray(lak_array == lak).nonzero()[0]
                mnum = np.unique(membership[idx])[0]
                membership[idx] = mnum

On test045_lake2tr split into four with pymetis.Options(seed=42, contig=1), two lakes and
90 lake connection cells:

active cells per model largest / mean cells moved
A, current from metis 552, 559, 559, 557 1.004
B, current after adjustment 666, 445, 641, 475 1.196 46
C, adjusted to the majority model 475, 636, 641, 475 1.151 37
D, proposed enhancement 550, 566, 566, 545 1.017 0

The letters are the panels of the figure below.

One model ends up with 20 percent more work than the mean.

Consolidating a lake is necessary, a lake has one stage and cannot be divided between models.
What costs the balance is doing it after the partition is built.

Proposed

Contract each lake to a single vertex before calling metis, carrying the summed vertex weight
and the union of its adjacency, and expand after partitioning (D). Each lake is then in one model
by construction, metis accounts for its weight while balancing, and no cell is moved
afterward. Metis also routes the partition boundaries around the lakes rather than through
them, so the result is not the original partition with the lakes patched, it is a different
and better partition. This is the same idea as weighting the barrier faces in #2826, state
the constraint before partitioning rather than repairing the result.

np.unique(...)[0] is the lowest numbered model the lake touches rather than the model that
holds most of it, so the loop also moves more cells than it needs to and biases toward the
low numbered models. Adjusting each lake to the majority model, the one that already holds
most of it, was measured (C) and recovers about a quarter of the lost balance, because it
changes which models are overloaded rather than whether they are. It is worth applying only
as the destination rule if the loop is kept as a fallback, and it is not a step toward the
contraction.

SFR and UZF are not affected. They are not consolidated, only weighted at
model_splitter.py:695, and reaches are allowed to span models.

Activity

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions