Source code for ogstools.meshlib.ip_mesh

from pathlib import Path
from typing import TypeVar

import numpy as np
import ogs
import pyvista as pv

from ogstools.definitions import SPATIAL_UNITS_KEY

Mesh = TypeVar("Mesh", bound=pv.UnstructuredGrid)


def _tessellation_map(cell_type: pv.CellType, integration_order: int) -> list:
    """Return the list of point ids, which form tessellated cells.

    For now only 2D elements are covered.
    """
    if integration_order == 2:
        if cell_type == pv.CellType.TRIANGLE:
            return [[0, 3, 6, 5], [1, 4, 6, 3], [2, 5, 6, 4]]
        if cell_type == pv.CellType.QUAD:
            return [[0, 4, 8, 7], [1, 5, 8, 4], [2, 6, 8, 5], [3, 7, 8, 6]]
    if integration_order == 3:
        if cell_type == pv.CellType.TRIANGLE:
            return [[0, 3, 5], [1, 4, 3], [2, 5, 4], [3, 4, 5]]
        if cell_type in [pv.CellType.QUAD, pv.CellType.QUADRATIC_QUAD]:
            return [
                [0, 4, 12, 11],
                [1, 5, 13, 8],
                [2, 6, 14, 9],
                [3, 7, 15, 10],
                [4, 8, 13, 12],
                [5, 9, 14, 13],
                [6, 10, 15, 14],
                [7, 11, 12, 15],
                [12, 13, 14, 15],  # fmt:skip
            ]
    if integration_order == 4:
        if cell_type in [pv.CellType.TRIANGLE, pv.CellType.QUADRATIC_TRIANGLE]:
            return [
                [0, 3, 9, 8],
                [1, 4, 10, 6],
                [2, 5, 11, 7],
                [3, 6, 10, 9],
                [4, 7, 11, 10],
                [5, 8, 9, 11],
                [9, 10, 11],  # fmt:skip
            ]
        if cell_type in [pv.CellType.QUAD, pv.CellType.QUADRATIC_QUAD]:
            return [
                [0, 4, 16, 15],
                [1, 5, 17, 12],
                [2, 6, 18, 13],
                [3, 7, 19, 14],
                [4, 8, 20, 16],
                [5, 9, 21, 17],
                [6, 10, 22, 18],
                [7, 11, 23, 19],
                [8, 12, 17, 20],
                [9, 13, 18, 21],
                [10, 14, 19, 22],
                [11, 15, 16, 23],
                [16, 20, 24, 23],
                [17, 21, 24, 20],
                [18, 22, 24, 21],
                [19, 23, 24, 22],  # fmt:skip
            ]
    msg = f"Tessellation not implemented ({cell_type=}, {integration_order=})."
    raise TypeError(msg)


# The following functions create the points which form the cells of the
# tessellated mesh.


def _tri_quad_2(mesh: pv.DataSet) -> np.ndarray:
    corners = np.asarray([cell.points for cell in mesh.cell])
    edge_centers = 0.5 * (corners + np.roll(corners, shift=-1, axis=1))
    cell_centers = np.reshape(mesh.cell_centers().points, (-1, 1, 3))
    return np.concatenate([corners, edge_centers, cell_centers], axis=1)


def _tri_3(mesh: pv.DataSet) -> np.ndarray:
    corners = np.asarray([cell.points for cell in mesh.cell])
    edge_centers = 0.5 * (corners + np.roll(corners, shift=-1, axis=1))
    return np.concatenate([corners, edge_centers], axis=1)


def _tri_4(mesh: pv.DataSet) -> np.ndarray:
    points = np.asarray([cell.points[:3] for cell in mesh.cell])
    edge_vecs = np.roll(points, shift=-1, axis=1) - points
    edge_mids = points + edge_vecs * 1.0 / 2.0
    edge_thirds1 = points + edge_vecs * 1.0 / 3.0
    edge_thirds2 = points + edge_vecs * 2.0 / 3.0
    mid_vecs = edge_mids[:, [1, 2, 0]] - points
    mid_points = points + mid_vecs * 2.0 / 5.0
    return np.concatenate(
        [points, edge_thirds1, edge_thirds2, mid_points], axis=1
    )


def _quad_3(mesh: pv.DataSet) -> np.ndarray:
    corners = np.asarray([cell.points[:4] for cell in mesh.cell])
    edge_vecs = np.roll(corners, shift=-1, axis=1) - corners
    edge_thirds1 = corners + edge_vecs * 1.0 / 3.0
    edge_thirds2 = corners + edge_vecs * 2.0 / 3.0
    mid_vecs = np.roll(edge_thirds2, shift=-2, axis=1) - edge_thirds1
    mid_thirds = edge_thirds1 + mid_vecs * 1.0 / 3.0
    return np.concatenate(
        [corners, edge_thirds1, edge_thirds2, mid_thirds], axis=1
    )


def _quad_4(mesh: pv.DataSet) -> np.ndarray:
    corners = np.asarray([cell.points[:4] for cell in mesh.cell])
    edge_vecs = np.roll(corners, shift=-1, axis=1) - corners
    edge_mids1 = corners + edge_vecs * 1.0 / 4.0
    edge_mids2 = corners + edge_vecs * 2.0 / 4.0
    edge_mids3 = corners + edge_vecs * 3.0 / 4.0
    mids1_vecs = np.roll(edge_mids3, shift=-2, axis=1) - edge_mids1
    mid_corners = edge_mids1 + mids1_vecs * 1.0 / 4.0
    mids2_vecs = np.roll(edge_mids2, shift=-2, axis=1) - edge_mids2
    mid_mids = edge_mids2 + mids2_vecs * 1.0 / 4.0
    mid_center = (edge_mids2 + mids2_vecs * 1.0 / 2.0)[:, :1]
    return np.concatenate(
        [
            corners,
            edge_mids1,
            edge_mids2,
            edge_mids3,
            mid_corners,
            mid_mids,
            mid_center,
        ],
        axis=1,
    )


def _compute_points(
    mesh: pv.DataSet, cell_type: pv.CellType, integration_order: int
) -> np.ndarray:
    "Create the points for the cells of the tessellated mesh."
    if integration_order == 2 and cell_type in [
        pv.CellType.TRIANGLE,
        pv.CellType.QUAD,
    ]:
        return _tri_quad_2(mesh)
    if integration_order == 3:
        if cell_type == pv.CellType.TRIANGLE:
            return _tri_3(mesh)
        if cell_type == pv.CellType.QUAD:
            return _quad_3(mesh)
    if integration_order == 4:
        if cell_type in [pv.CellType.TRIANGLE, pv.CellType.QUADRATIC_TRIANGLE]:
            return _tri_4(mesh)
        if cell_type in [pv.CellType.QUAD, pv.CellType.QUADRATIC_QUAD]:
            return _quad_4(mesh)
    msg = f"Tessellation not implemented ({cell_type=}, {integration_order=})."
    raise TypeError(msg)


def _connectivity(
    mesh: pv.DataSet, points: np.ndarray, subcell_ids: list
) -> list:
    connectivity = []
    for index in range(mesh.number_of_cells):
        offset = index * points.shape[1]
        for ids in subcell_ids:
            connectivity += [len(ids)] + (np.asarray(ids) + offset).astype(
                int
            ).tolist()
    return connectivity


[docs] def tessellate( mesh: pv.DataSet, cell_type: pv.CellType, integration_order: int ) -> pv.PolyData: "Create a tessellated mesh with one subcell per integration point." subcell_ids = _tessellation_map(cell_type, integration_order) points = _compute_points(mesh, cell_type, integration_order) connectivity = _connectivity(mesh, points, subcell_ids) return pv.PolyData( np.reshape(points, (-1, 3)), faces=connectivity, n_faces=len(subcell_ids) * points.shape[0], )
[docs] def to_ip_point_cloud(mesh: Mesh) -> pv.UnstructuredGrid: "Convert integration point data to a pyvista point cloud." # ipDataToPointCloud can't handle this bad_keys = [ SPATIAL_UNITS_KEY, "material_state_variable_ElasticStrain_ip", "free_energy_density_ip", ] _mesh = mesh.copy() for key in bad_keys: if key in _mesh.field_data: _mesh.field_data.remove(key) parentpath = Path() if mesh.filepath is None else mesh.filepath.parent input_file = parentpath / "ipDataToPointCloud_input.vtu" _mesh.save(input_file) output_file = parentpath / "ip_mesh.vtu" ogs.cli.ipDataToPointCloud(i=str(input_file), o=str(output_file)) return pv.XMLUnstructuredGridReader(output_file).read()
[docs] def to_ip_mesh(mesh: Mesh) -> pv.UnstructuredGrid: "Create a mesh with cells centered around integration points." meta = mesh.field_data["IntegrationPointMetaData"] meta_str = "".join([chr(val) for val in meta]) integration_order = int( meta_str.split('"integration_order":')[1].split(",")[0] ) ip_mesh = to_ip_point_cloud(mesh) cell_types = list({cell.type for cell in mesh.cell}) new_meshes: list[pv.PolyData] = [] for cell_type in cell_types: _mesh = mesh.extract_cells_by_type(cell_type) new_meshes += [tessellate(_mesh, cell_type, integration_order)] new_mesh = new_meshes[0] new_mesh.field_data[SPATIAL_UNITS_KEY] = mesh.field_data[SPATIAL_UNITS_KEY] for _mesh in new_meshes[1:]: new_mesh = new_mesh.merge(_mesh) new_mesh = new_mesh.clean() ordering = new_mesh.find_containing_cell(ip_mesh.points) ip_data = { k: v[np.argsort(ordering)] for k, v in ip_mesh.point_data.items() } new_mesh.cell_data.update(ip_data) return new_mesh