Skip to content

Calling components

Every component has two entry points, with the same options:

  • On files: inputs and outputs are files, named in the options, as on the command line.
  • In memory: inputs are meshes passed by name, outputs come back as meshes with a report, and nothing is written (no mesh, report, log or debug files).

On the same input, both give the same mesh; only the order of the cells in a .msh file can differ (it groups cells by region). The full specification is in the component I/O design notes.

The two routes

sphere = wm.read_mesh(str(DATA / "sphere.obj"))

# Memory: inputs by name, nothing written; returns the output meshes and a report.
outputs, report = wm.qslim({"input": "sphere", "target_rel": 0.5}, {"sphere": sphere})
print(list(outputs), report["vertices"], "vertices")

# Files: as wmtk_app does with the same options; returns nothing.
wm.qslim({"input": str(DATA / "sphere.obj"), "output": "sphere_half.obj", "target_rel": 0.5})

wm.<component>(options) works on files and returns nothing; wm.<component>(options, meshes) works in memory and returns (outputs, report). outputs["mesh"] is the result; some components return more meshes (TetWild also returns surface, its tracked surface, and envelope). The component releases the Python GIL while it runs; calls run one at a time per process.

const wmtk::MeshMap meshes{{"sphere", wmtk::io::read_mesh(data / "sphere.obj")}};
auto [outputs, report] =
    wmtk::components::qslim::qslim({{"input", "sphere"}, {"target_rel", 0.5}}, meshes);
std::cout << report["vertices"] << " vertices\n";
wmtk::io::write_mesh("sphere_half.msh", outputs.at("mesh"));

Each component has both overloads:

void name(nlohmann::json options);
ComponentResult name(nlohmann::json options, const MeshMap& meshes);

MeshMap is std::map<std::string, MeshData>; ComponentResult is the pair of output meshes and the JSON report.

wmtk_app -j options.json runs the file route. application names the component; input paths are relative to the JSON file, and outputs are written to the working directory.

In memory, the options name inputs instead of files ("input": "sphere") and need no application, output or input_dir.

Mesh data

A mesh is vertex positions and blocks of cells by type, each with optional named fields:

# Two triangles of the unit square, as a mesh dictionary: vertex positions (3 columns, 2D meshes
# have z = 0) and cell blocks by type.
square = {
    "V": np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]], dtype=np.float64),
    "cells": {
        "triangle": {"connectivity": np.array([[0, 1, 2], [0, 2, 3]], dtype=np.int64)},
    },
}
// Two triangles of the unit square: row-major positions with three columns, and cell blocks
// by type.
wmtk::MeshData square;
square.V.resize(4, 3);
square.V << 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0;
auto& triangles = square.cells[wmtk::CellType::Triangle].connectivity;
triangles.resize(2, 3);
triangles << 0, 1, 2, 0, 2, 3;
square.validate();
  • V: positions, one row per vertex and three columns (a 2D mesh has z = 0). Float64.
  • cells: blocks keyed by type, point, edge, triangle, quad, tetrahedron, prism or pyramid, each with a connectivity array (Int64, one row per cell) and optional attributes. Prism and pyramid corners are in VTK order.
  • vertex_attributes and a block's attributes: named fields, {"values": array, "labels": [...]}, Float64, Int64 or UInt8, one row per vertex or cell.

Regions are a UInt8 cell field tags, one column per region, labelled by its name. A cell can be in several regions:

mesh["cells"]["tetrahedron"]["attributes"] = {
    "tags": {"values": np.array([[1, 1], [1, 0]], dtype=np.uint8),
             "labels": ["region_a", "region_b"]},
}

See Concepts: regions for how components select regions by expressions of their names.

The bindings copy what they read into C++ arrays, so inputs may be read-only or strided; outputs own their arrays and can go straight into the next call.

Files

mesh = outputs["mesh"]
wm.write_mesh("sphere_half.msh", mesh)   # .msh or .vtu
again = wm.read_mesh("sphere_half.msh")  # .msh, .obj, .stl, .off, ...
assert np.allclose(again["V"], mesh["V"])

# A checkpoint: every output mesh of a result, read back exactly as written.
wm.save(outputs, "checkpoint/sphere_half")
restored = wm.load("checkpoint/sphere_half")

read_mesh reads .msh (Gmsh 4.1), .obj and libigl's surface formats (.stl, .off, .ply, ...); write_mesh writes .msh and .vtu. wm.save(outputs, stem) writes every output mesh of a result, and wm.load(stem) reads back exactly what it wrote, so a pipeline resumed from a checkpoint continues as it would have.

Reports

The report is a JSON object (a dictionary in Python): counts, timings, quality statistics, what the component logged. On the file route, the report option names a file to write it to. Each component's report is described with its tutorial.

Histories

With history_output, a component records every operation it commits. On files, the history goes to the file history_output names; in memory, any non-empty value records it and the report returns it as bytes:

outputs, report = wm.qslim(
    {"input": "sphere", "target_rel": 0.5, "history_output": "record"}, {"sphere": sphere})
history = report["history"]  # bytes: the record of every collapse
tracking = wm.Tracking(history)
print(tracking.build_warnings)

From a history, tracking maps points and fields between the input and the output.