Level-set cutting — cut_level_set, cut_quadrature
cut_level_set returns the part of a mesh that one or several level sets select: the pieces of the cells they cross, on one side of them or on their zero sets, and the cells wholly inside. cut_quadrature returns quadrature rules on that part, the cut-cell quadrature of unfitted finite elements (CutFEM). Both are CutCells' (S. Claus, ONERA, MIT), an optional dependency of the C++ core: without it both refuse by name, pointing at MESHIOPLUSPLUS_WITH_CUTCELLS.
import numpy as np
import meshioplusplus as mp
box = mp.box((8, 8, 8)) # hexahedra
box.point_data["ball"] = np.linalg.norm(box.points - 0.5, axis=1) - 0.3
inside, report = mp.cut_level_set(box, "ball", return_report=True)
print(report["measure"], report["num_cut_cells"]) # the ball's volume, its cut cells
skin = mp.cut_level_set(box, "ball", selection="ball = 0") # its surface
points = mp.cut_quadrature(box, "ball", order=3) # point_data quadrature:weight
mp.cut_has_cutcells() # False without CutCellsWhat it gives that the other cutting operations do not
sliceandisosurfacegive only the interface, by marching tetrahedra on simplices; higher-order cells are linearised first.cropkeeps whole cells.- MMG's
discretize_levelsetremeshes linear tetrahedra and triangles conformally to the level set and drops the data. cut_level_setcuts every cell family (line,triangle,quad,tetra,hexahedron,wedge,pyramid) into pieces of its own family (a hexahedron cut through two opposite faces stays one hexahedron), keeps the uncut cells as they are, combines several level sets with sharp edges where they meet, reads a quadratic level set ontriangle6andtetra10cells, and carries the data.
Level sets and the selection
- Level sets. Each is a scalar
point_dataarray (level_sets, one value per node) or a plane (planes:(origin, normal),(x - origin) . normalwith the normal normalised, namedplane0,plane1, ... after the arrays). A level set is linear in each cell's corners, and quadratic on a mesh whose cut cells are alltriangle6or alltetra10(all six or ten nodes; CutCells' Basix node order is converted from VTK's). - Selection. CutCells' expression over the level sets' names:
phi < 0,phi > 0,phi = 0(the zero set: an interface of one dimension less), joined withandandor, such as"wall < 0 and hole > 0"or"phi1 = 0 and phi2 < 0"(the part of one interface inside another level set). Empty means"<first> < 0". Names must start with a letter and hold letters, digits and_; an unknown name or a syntax error is refused naming the level sets. - Cells. The cells of the highest topological dimension are cut; lower-dimensional blocks (a volume mesh's boundary triangles, say) are left out with a warning. Polygons and polyhedra are refused (
convert_cells(mode="simplexify")first). Geometry is straight-sided: a curved higher-order cell is cut through its corners, and its other nodes only carry the quadratic level set and its data.
Backends and output
lutcuts each cell by lookup tables into straight pieces (triangulate=Truesplits them, and the whole cells kept with them, into tetrahedra or triangles; CutCells itself splits only the interface quadrilaterals). It is exact for a level set that is linear in each cell: a planar cut's volume, area and interface come out to rounding.quadraysintegrates curved interfaces to high order (Bernstein bounds certify the level set's sign on boxes, which are bisected where they cannot), andcut_level_setgives its pieces as Lagrange cells ofdegree(VTK_LAGRANGE_HEXAHEDRON,..._QUADRILATERAL,..._CURVE), the curved interface included."auto", the default, takes quadrays for a quadratic level set and the lookup tables otherwise.- The mesh. Pieces are welded (CutCells gives each its own vertices), grouped into one block per type, and carry
cut:parent_cell(the input cell, global block-major) andcut:cut(1 on a piece of a cut cell, 0 on a whole cell).point_datais interpolated in each new vertex's parent with the parent's shape functions (quadratic ones ontriangle6/tetra10with a quadratic level set; integer arrays take the nearest node's value),cell_dataand cell regions follow the parent, point and side regions are dropped with a warning, andfield_datais kept. - The report.
num_cells(cells of the cut dimension),num_cut_cells,num_uncut_cells(wholly in the part),num_pieces(output cells),dimensionof the part,measure(its volume, area or length, by a degree-1 rule of the same backend),selection,backendandwarnings.
Quadrature
cut_quadrature(mesh, ..., order=k) returns rules of degree k (exact for polynomials of degree k on straight pieces) on the part: one vertex per point at its physical position, with point data quadrature:weight (physical: the weights of the part sum to its measure), quadrature:reference (the point in the parent cell's reference element, tdim components), quadrature:rule (one rule per covered cell piece) and quadrature:parent_cell. Uncut cells inside the part get their own full-cell rules unless include_uncut=False. The report gives num_rules, num_points, sum_weights, dimension, selection, backend and warnings.
Measured
On a unit cube cut by the plane x = 0.3, every family (hexahedra, tetrahedra, wedges, the cube as six pyramids, and quads and triangles in 2-D) gives the volume 0.3 on one side, 0.7 on the other and the interface area 1, all to 1e-12; two planes give the quarter, three quarters and half faces their selections ask for. A sphere of radius 0.3 on boxes of 4, 8 and 16 tetrahedra a side converges at second order (the error at least 2.5 times smaller each halving), and the same sphere as a quadratic level set on tetra10 cells is at least five times more accurate than the linear one on the same mesh (tests/cpp/test_cut.cpp).
Building with CutCells
CutCells is bring-your-own, never vendored or downloaded by CMake. build/build-cutcells.sh PREFIX builds the commit build/deps-pins.sh pins (CutCells has no release tags), static and without OpenMP; then configure meshio++ with -DMESHIOPLUSPLUS_WITH_CUTCELLS=ON -DCUTCELLS_ROOT=PREFIX (or build/configure.sh --cutcells-root PREFIX). The PyPI wheels for Linux, macOS and Windows link it (build/build-permissive-libs.sh); the Windows wheels link it too (a static MSVC build, build/build-cutcells.sh), and so does the full WebAssembly artifact (build/build-wasm-deps.sh --full); the lean WebAssembly artifact and the default source build do not, and refuse by name. cut_has_cutcells() (C++, Python), _core.__has_cutcells__, mio_has_cutcells() (C) and their binding twins report it.
Bindings
- C++:
meshioplusplus/operations/cut.hpp:cut_level_set(mesh, CutOptions),cut_quadrature,cut_has_cutcells. - Python:
cut_level_set(mesh, level_sets, planes=, selection=, include_uncut=, backend=, degree=, triangulate=, record_parent_ids=, return_report=)andcut_quadrature(..., order=). - CLIs:
meshioplusplus cut IN OUT --level-set NAME --plane OX,OY,OZ,NX,NY,NZ --selection EXPR [--cut-only] [--backend B] [--degree K] [--triangulate] [--json]andcut-quadrature ... --order K, in both CLIs. - Pipeline: the
CutLevelSetstep (LevelSets,Planesas six numbers per plane,Selection,IncludeUncut,Backend,Degree,Triangulate,RecordParentIds), in both engines. - MCP: the
cut_level_setandcut_quadraturetools (planes as six numbers each), reportingcutcells_available. - C:
mio_cut_level_setandmio_cut_quadraturewithmio_cut_opts(104 bytes,mio_cut_opts_init) andmio_cut_report(104 bytes), andmio_has_cutcells; Fortran, Julia, R and WASM (cutLevelSet,cutQuadrature,hasCutcells) wrap them.
Limits
Straight-sided geometry only (CutCells' affine cells); polygons and polyhedra are refused; quadratic level sets only on meshes of triangle6 or tetra10 cells; at most 64 level sets in one cut (CutCells' mask); serial. CutCells is in the full WebAssembly artifact only (static, built without OpenMP, with -fwasm-exceptions); the lean one refuses by name.