Module underworld3.adaptivity

Functions

def mesh2mesh_meshVariable(meshVar0, meshVar1, verbose=False)
Expand source code
def mesh2mesh_meshVariable(meshVar0, meshVar1, verbose=False):
    """Map a meshVar on mesh0 to a meshVar on mesh1 using
    an intermediary (temporary) swarm"""

    mesh0 = meshVar0.mesh
    mesh1 = meshVar1.mesh

    # 1 Create a temporary swarm with a variable that matches meshVar0

    tmp_swarm = uw.swarm.Swarm(mesh0)
    var_name = rf"{meshVar0.clean_name}"
    var_cpts = meshVar0.num_components

    tmp_varS = uw.swarm.SwarmVariable(
        var_name, tmp_swarm, size = (1,var_cpts), vtype=uw.VarType.MATRIX, _proxy=False
    )

    # Maybe 3+ if var is higher order ??
    tmp_swarm.populate(fill_param=3)


    # Set data on the swarmVar

    # print(f"Map data to swarm (rbf) - points = {tmp_swarm.dm.getSize()}", flush=True)

    with tmp_swarm.access(tmp_varS):
        tmp_varS.data[...] = meshVar0.rbf_interpolate(
            tmp_swarm.particle_coordinates.data
        )

    # print(f"Distribute swarm", flush=True)

    # Now ship this out to the other mesh via the tmp swarm
    tmp_swarm1 = mesh2mesh_swarm(
        mesh0, mesh1, tmp_swarm, [tmp_varS], proxy=False, verbose=verbose
    )

    if hasattr(tmp_swarm1, "_meshVar"):
        raise RuntimeError("Swarm should not have a proxy on it !")

    tmp_swarm1.vars[var_name]._rbf_to_meshVar(meshVar1)

    del tmp_swarm, tmp_swarm1

    return

Map a meshVar on mesh0 to a meshVar on mesh1 using an intermediary (temporary) swarm

def mesh2mesh_swarm(mesh0, mesh1, swarm0, swarmVarList, proxy=True, verbose=False)
Expand source code
def mesh2mesh_swarm(mesh0, mesh1, swarm0, swarmVarList, proxy=True, verbose=False):
    """Warning [NSFW] - this uses EXPLICIT message passing calls to handle the
    situation where a swarm cell_dm cannot find particles after mesh redistribution.
    This occurs when particles are moved accross non-neighbouring processes or if the
    mesh neighbours are redistricted. This should be fixed at the DMSwarm / DMPlex level
    so this code is just a placeholder. Or maybe it's just user error !

    Notes 1: This copies a swarm from one mesh to another allowing for completely incommensurate
    partitionings. Warning: this is not always a 1->1 mapping. There may be some duplication and
    particles may go missing along curved boundaries where the meshes do not necessarily overlap.
    The same is true in the shadow spaces.

    Note 2: The swarm is "adapted" to the original mesh and will need
    to be repopulated on the new one, or data can be mapped to a purpose-built swarm.

    Note 3: We pass the data around as floats for the time being. Be careful when converting back.

    Note 4: set proxy=True to automatically generate proxy variables on mesh1 but consider skipping
    if the returned swarm is ephemeral
    """

    with swarm0.access():
        swarm_data = swarm0.particle_coordinates.data.copy()
        for swarmVar in swarmVarList:
            swarm_data = np.hstack(
                (swarm_data, np.ascontiguousarray(swarmVar.data.astype(float)))
            )

    s_coords0 = np.ascontiguousarray(swarm_data[:, 0 : mesh0.dim])

    cell = mesh1.get_closest_local_cells(s_coords0)
    found = np.where(cell >= 0)[0]
    not_found = np.where(cell == -1)[0]

    n_found = found.shape[0]
    n_not_found = not_found.shape[0]

    # print(
    #     f"{uw.mpi.rank} - A/local found: {n_found} v. not found: {n_not_found}",
    #     flush=False,
    # )

    # Let's sync the number of missing points by rank

    mpi_size = uw.mpi.size

    missing_points = np.zeros((mpi_size))
    comm = uw.mpi.comm
    root = 0

    local_array = n_not_found
    # print(f"rank: {uw.mpi.rank}, local_array size: {n_not_found}")

    sendbuf = swarm_data[not_found]
    global_sizes = np.empty(mpi_size, dtype=int)
    local_size = np.array([sendbuf.shape[0]], dtype=int)

    comm.Allgather(local_size, global_sizes)

    global_size = global_sizes.sum()
    # print(
    #     f"{uw.mpi.rank} Sizes are: {global_sizes} Buffer size: {global_size}",
    #     flush=True,
    # )

    uw.mpi.barrier()

    recvbuf = np.empty((global_size, sendbuf.shape[1]), dtype=float)

    comm.Allgatherv(sendbuf=sendbuf, recvbuf=(recvbuf, global_sizes * sendbuf.shape[1]))

    global_unallocated_coords = recvbuf[:, 0 : mesh0.dim].copy()
    global_unallocated_data = recvbuf.copy()

    ## ====================================
    ## Now we have all the information here
    ## and we can hand it out to the new swarm
    ## =====================================

    # if swarm0.recycle_rate > 0:
    #     if swarm0.vars["DMSwarm_Xorig"] not in swarmVarList:
    #         swarmVarList.append(swarm0.vars["DMSwarm_Xorig"])

    swarm1 = uw.swarm.Swarm(mesh=mesh1, recycle_rate=swarm0.recycle_rate)

    for swarmVar in swarmVarList:
        uw.swarm.SwarmVariable(
            swarmVar.name,
            swarm1,
            size=swarmVar.shape,
            vtype=swarmVar.vtype,
            dtype=swarmVar.dtype,
            proxy_degree=swarmVar._proxy_degree,
            proxy_continuous=swarmVar._proxy_continuous,
            _proxy=proxy,
            _register=True,
            rebuild_on_cycle=swarmVar._rebuild_on_cycle,
        )

    swarm1.dm.finalizeFieldRegister()

    # First we add the found points (there are n_found of those)

    found_coords = s_coords0[found]
    adds = n_found + 1

    swarm1.dm.addNPoints(adds)

    ## Update cells etc, but don't migrate as
    ## we don't want to distrupt the data layout

    cellid = swarm1.dm.getField("DMSwarm_cellid")
    coords = swarm1.dm.getField("DMSwarmPIC_coor").reshape((-1, swarm1.dim))

    coords[...] = found_coords[...]
    if n_found > 0:
        cellid[:] = mesh1.get_closest_cells(coords)  ## found points

    swarm1.dm.restoreField("DMSwarmPIC_coor")
    swarm1.dm.restoreField("DMSwarm_cellid")

    # Add in the data fields for the found points

    offset = swarm1.dim
    for swarmVar in swarmVarList:
        varField = swarm1.dm.getField(swarmVar.clean_name)
        varCpts = swarmVar.num_components
        varData = swarm_data[:, offset : offset + varCpts]
        fieldData = varField.reshape(-1, varCpts)

        if n_found > 0:
            fieldData[:, :] = swarm_data[found, offset : offset + varCpts].astype(
                swarmVar.dtype
            )

        swarm1.dm.restoreField(swarmVar.clean_name)
        offset += varCpts

    uw.mpi.barrier()

    ## Now deal with all the points we caught
    ## from the broadcast. Again, we only own
    ## some of these. So let's add them now

    cell = mesh1.get_closest_local_cells(global_unallocated_coords)
    found1 = np.where(cell >= 0)[0]
    not_found1 = np.where(cell == -1)[0]

    n_found1 = found1.shape[0]
    n_not_found1 = not_found1.shape[0]

    psize = swarm1.dm.getLocalSize()
    adds = n_found1 + 1

    swarm1.dm.addNPoints(adds)

    cellid = swarm1.dm.getField("DMSwarm_cellid")
    coords = swarm1.dm.getField("DMSwarmPIC_coor").reshape((-1, swarm1.dim))

    coords[psize + 1 :, :] = global_unallocated_coords[found1, :]

    if n_found1 > 0:
        cellid[psize + 1 :] = cell[found1]  ## gathered points

    swarm1.dm.restoreField("DMSwarm_cellid")
    swarm1.dm.restoreField("DMSwarmPIC_coor")

    # print(
    #     f"{uw.mpi.rank}/i: {swarm1.dm.getLocalSize()} / {swarm1.dm.getSize()} cf {swarm0.dm.getSize()}",
    #     flush=True,
    # )

    uw.mpi.barrier()
    ## Now the variables from the broadcast

    offset = swarm1.dim
    for swarmVar in swarmVarList:
        varField = swarm1.dm.getField(swarmVar.clean_name)
        varCpts = swarmVar.num_components
        varData = global_unallocated_data[:, offset : offset + varCpts]
        fieldData = varField.reshape(-1, varCpts)

        if n_found1 > 0:
            fieldData[psize + 1 :, :] = global_unallocated_data[
                found1, offset : offset + varCpts
            ]

        swarm1.dm.restoreField(swarmVar.clean_name)
        offset += varCpts

    ## Update proxy variables if present
    for swarm1Var in swarm1.vars.values():
        swarm1Var._update()

    # print(
    #     f"{uw.mpi.rank}-1: Swarm Size (Local) {swarm0.dm.getLocalSize()}; (Global) {swarm1.dm.getSize()}"
    # )
    uw.mpi.barrier()
    swarm1.dm.migrate(remove_sent_points=True)

    # print(
    #     f"{uw.mpi.rank}-2: Swarm Size (Local) {swarm0.dm.getLocalSize()}; (Global) {swarm1.dm.getSize()}"
    # )

    if verbose:
        size0 = swarm0.dm.getSize()
        size1 = swarm1.dm.getSize()

        print("---------", flush=True)

        if uw.mpi.rank == 0:
            print(f"Swarm0: {size0}; Swarm1: {size1}", flush=True)
            print("---------", flush=True)

        uw.mpi.barrier()

        print(
            f"{uw.mpi.rank} - Local data size {swarm_data.shape}\n"
            f"{uw.mpi.rank} - Exchanged data size {recvbuf.shape}\n"
            f"{uw.mpi.rank} - Local data found: {n_found} v. not found: {n_not_found}\n"
            f"{uw.mpi.rank} - Exchanged data : {n_found1} v. not found: {n_not_found1}",
            flush=True,
        )

        print(f"{uw.mpi.rank} - Variables defined on the new swarm:")
        for swarmVar in swarmVarList:
            print(
                f"{uw.mpi.rank} -    {swarmVar.clean_name} ({swarmVar.dtype} * {swarmVar.num_components})"
            )

        print("---------", flush=True)

        uw.mpi.barrier()

    return swarm1

Warning [NSFW] - this uses EXPLICIT message passing calls to handle the situation where a swarm cell_dm cannot find particles after mesh redistribution. This occurs when particles are moved accross non-neighbouring processes or if the mesh neighbours are redistricted. This should be fixed at the DMSwarm / DMPlex level so this code is just a placeholder. Or maybe it's just user error !

Notes 1: This copies a swarm from one mesh to another allowing for completely incommensurate partitionings. Warning: this is not always a 1->1 mapping. There may be some duplication and particles may go missing along curved boundaries where the meshes do not necessarily overlap. The same is true in the shadow spaces.

Note 2: The swarm is "adapted" to the original mesh and will need to be repopulated on the new one, or data can be mapped to a purpose-built swarm.

Note 3: We pass the data around as floats for the time being. Be careful when converting back.

Note 4: set proxy=True to automatically generate proxy variables on mesh1 but consider skipping if the returned swarm is ephemeral

def mesh_adapt_meshVar(mesh, meshVarH, metricVar, verbose=False, redistribute=False)
Expand source code
def mesh_adapt_meshVar(mesh, meshVarH, metricVar, verbose=False, redistribute=False):
    # Create / use a field on the old mesh to hold the metric
    # Perhaps that should be a user-definition

    boundaries = mesh.boundaries

    # The metric
    field_id = metricVar.field_id
    hvec = meshVarH._lvec
    metric_vec = mesh.dm.metricCreateIsotropic(hvec, field_id)

    if verbose:
        print(f"{uw.mpi.rank} dm adaptation ... begin", flush=True)

    _dm_stack_bcs(mesh.dm, boundaries, "CombinedBoundaries")
    dm_a = mesh.dm.adaptMetric(metric_vec, bdLabel="CombinedBoundaries")

    icoord_vec = dm_a.getCoordinates()

    _dm_unstack_bcs(dm_a, boundaries, "CombinedBoundaries")

    if verbose:
        print(f"{uw.mpi.rank} dm adaptation ... complete", flush=True)

    meshA = uw.meshing.Mesh(
        dm_a,
        simplex=mesh.dm.isSimplex,
        coordinate_system_type=mesh.CoordinateSystem.coordinate_type,
        qdegree=mesh.qdegree,
        refinement=None,
        refinement_callback=mesh.refinement_callback,
        boundaries=mesh.boundaries,
        distribute=redistribute,
    )

    if verbose:
        print(f"{uw.mpi.rank} mesh adaptation / distribution ... complete", flush=True)

    return icoord_vec, meshA