Algorithms¶
Every algorithm of tack.dataset, by what it does, with its parameters. Each
group opens with a table: what each algorithm gives, what it takes (a
DataSet; pieces, a PartitionedDataSet, over MPI ranks or not; an
AMRDataSet; an OctreeDataSet), and its nearest counterpart in VTK and in
Viskores. Below the table, each algorithm's signature and description.
import tack.dataset as td
hot = td.threshold(data, "temperature", 300.0, 400.0)
grad = td.gradients(data, "pressure", at="points")
s = td.statistics(data, "pressure")
Every algorithm is in tack.dataset.algorithms, one module each, and
tack.dataset re-exports them all. They share conventions, set out in the
Dataset API reference's Algorithms and filters:
- the dataset comes first, and a field argument is a
Fieldor the name of one of the dataset's fields; - an algorithm that makes a new dataset takes
fields=: which fields to carry (all by default, a name, a list of names, or[]for none), carried by the rules in how fields carry through filters; - an algorithm that computes a field returns a
Field, several fields a named tuple of them, a summary Python numbers, a selection aSet; - nothing changes its input, except where an entry says "in place";
- every algorithm runs, and allocates, on its dataset's device; algorithms over pieces run each piece on its own device;
- hidden cells are never processed, and ghost cells and points are left out of summaries;
- a parameter that is a length, a time or a field's value (a radius, a step
size, a threshold's range) also takes a quantity (
"10 cm",(10, "cm")), converted to the dataset's or the field's unit; a plain number is taken as it is (Units).
Which algorithms work on which topologies (polyhedra, polygons, polylines) is in the Dataset API reference's support table. The classes the algorithms take and give -- datasets, fields, spaces, topologies, sets, implicit functions, locators -- are in Data Model.
Contents¶
- Cells kept whole: New datasets of some of the input's cells, unchanged, with only the points they use.
- Cutting: New datasets of pieces of cells, with points made on their edges.
- Surfaces and edges: A mesh's faces and edges, as sets or datasets on the same points.
- Connectivity and winding: Which cells belong together, and which are wound wrong.
- Changing cell types: The same points and region, in other cells.
- Moving the geometry: New positions for the same topology.
- Measures and quality: Fields describing the mesh itself.
- Fields from fields: New fields computed from a dataset's fields.
- Finite volumes: Values on faces and the cells around them.
- Summaries: Python numbers about a field, over every piece and rank.
- Probing, sampling and particles: Values at other points, and paths through a velocity field.
- Point clouds: Points as vertex cells: neighbours found through a
PointLocator. - Pieces and ghosts: A
PartitionedDataSet's pieces, over MPI ranks or not. - Adaptive mesh refinement: Algorithms only an
AMRDataSettakes;ghost_cells,probe,statisticsand the rest above take one too.
Cells kept whole¶
New datasets of some of the input's cells, unchanged, with only the points they use.
| Algorithm | Gives | Takes | VTK | Viskores |
|---|---|---|---|---|
threshold |
cells whose field lies in a range | dataset | vtkThreshold | Threshold |
extract_cells |
cells by id, array or cell set | dataset | vtkExtractCells | — |
extract_geometry |
cells inside (or outside, or cut by) an implicit function | dataset | vtkExtractGeometry | ExtractGeometry |
extract_extent |
a box of a structured grid, every n-th point | dataset | vtkExtractGrid | ExtractStructured |
mask |
every n-th cell | dataset | — | Mask |
threshold ¶
threshold(
data, field, lower, upper, all_points=True, fields=None
)
The cells whose field lies in [lower, upper], with only the points they use.
field is a scalar field or its name. A cell field (Constant, values
on cells) keeps a cell by its value; a field with a basis (H1, L2
of any orders) or values on points by its values at the cell's corners:
all of them in range, or with all_points=False any (VTK's
AllScalars). The result's cells keep their shapes and order; its points
are the used ones, renumbered in order. Every field it can carry comes
along: point fields gathered to the kept points, cell fields to the kept
cells, L2 fields (and an L2 geometry) block by block, keeping each
cell's order. Fields on faces and edges, which the new topology derives
afresh, are left behind.
On a polyhedral topology the kept cells keep their faces, stored once and
numbered in their old order, so values on faces come along. A face whose
side-0 cell is dropped is turned around for its remaining cell, and its
oriented values (Values(..., "faces", oriented=True)) are negated. A
polygonal topology's kept polygons keep their loops, and a polyline
topology's polylines their chains; their facets' values, whose numbering is
derived afresh, are left behind.
Source code in packages/tack-vis/src/tack/dataset/algorithms/threshold.py
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | |
extract_cells ¶
extract_cells(data, cells, fields=None)
The cells with the given ids (a host array, an integer field, or a cell
Set or its name in data.sets), with only the points they use; cells
keep their order, not the order given.
Source code in packages/tack-vis/src/tack/dataset/algorithms/extract_cells.py
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | |
extract_geometry ¶
extract_geometry(
data, function, inside=True, boundary=False, fields=None
)
The whole cells inside function's region (all their points at or below zero),
or, with inside=False, outside it (all above); with boundary, also the
cells it cuts (some point on the kept side). Cells are not split -- that is
clip. Points are compacted and fields carried, as by threshold.
Source code in packages/tack-vis/src/tack/dataset/algorithms/extract_geometry.py
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
extract_extent ¶
extract_extent(data, extent, rate=1, fields=None)
The points of data, a structured grid, in the box extent -- one
(low, high) pair of point indices per axis, both included, or VTK's flat
(i0, i1, j0, j1, k0, k1), in the whole grid's indices (as VTK's extents
count them, from the topology's offset), clipped to the grid -- every
rate-th along each axis (a number, or one per axis): a structured grid
of the same kind, an image's coordinates still uniform, a rectilinear
grid's axes cut, explicit points taken. VTK's vtkExtractGrid.
An axis of one point is dropped: a k-plane of a 3D grid is a 2D grid,
whose cells are faces of the 3D grid's. Point fields come along at the
points kept; each cell takes the cell fields, marks and ids of the input
cell at its first corner -- the cell beyond a plane, or before it at the
grid's far side -- as VTK takes them. The output's offset is its first
point's index, so its extent is the box's (with rate 1).
Source code in packages/tack-vis/src/tack/dataset/algorithms/extract_extent.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | |
mask ¶
mask(data, stride, fields=None)
Every stride-th cell (0, stride, 2 * stride, ...), as Viskores' Mask,
with only the points they use: fields carry as threshold carries them.
mask_points is its point twin.
Source code in packages/tack-vis/src/tack/dataset/algorithms/mask.py
10 11 12 13 14 15 16 17 18 19 20 | |
Cutting¶
New datasets of pieces of cells, with points made on their edges.
| Algorithm | Gives | Takes | VTK | Viskores |
|---|---|---|---|---|
contour |
isosurfaces, isolines or points of a field, one level or several | dataset | vtkContourFilter | Contour |
slice |
where an implicit function is zero (or a level) | dataset | vtkCutter | Slice |
slice_plane |
a plane's cut, or parallel planes | dataset | vtkCutter | Slice |
clip |
the part above (or below) a field's level or inside an implicit function | dataset | vtkTableBasedClipDataSet | ClipWithField, ClipWithImplicitFunction |
contour ¶
contour(
data,
field,
value,
merge_points=True,
fields=None,
dimension=None,
)
Where field crosses value: a surface of a 3D mesh, as triangles
(as polygons on polyhedra); lines of a 2D mesh.
value may be a sequence: the surfaces of every value, in one dataset
made in one pass over the cells (each cell compared with every value),
with each output cell's value in a cell field contour_value. Points
are not shared between values.
field is a scalar field with a basis, or its name: H1 of order 1
(point data), or L2 (DG, an order per cell too). An order-2 H1
field is refused: its cells would need subdividing, and their corner
values alone are another, linear field. Each 3D cell contributes the
triangles of VTK's case tables for its corner values, interpolated along
its edges; in a mesh with 3D cells, cells of lower dimension contribute
nothing, and a cell constant has no level set inside its cells.
dimension (3, 2 or 1) chooses which cells to contour, by default the
mesh's highest: the cells of that dimension alone, as a mesh of their own on
the points they use (no cells of it, no contour), so a mesh of tetrahedra
and the triangles of its boundary gives the tetrahedra's surface, or with
dimension=2 the triangles' lines. Cells of other dimensions are never
mixed in.
A 2D mesh -- triangles, quads, pixels or polygons -- gives line cells from
its point values: each cell's crossings, found going round its edges, are
joined in pairs, each line with the higher values on its right as the
cell winds. Lines share their points through the mesh's faces (its edges),
whatever merge_points says -- unless the field or the geometry is
L2: then each cell's lines have their own points, from its own values
and corners. No cells give no lines.
A 1D mesh -- lines or polylines -- gives a vertex cell at each crossing of
its segments, from its point values, interpolated from the lower point id;
a crossing at a point whose value is the level is that point, and with
merge_points every crossing is one vertex, each taking the cell fields
of the first cell to cross there.
With merge_points, triangles share their points: by global edge when
the field and the geometry are both continuous, so the surface is
watertight; otherwise only within each cell, so a DG field's surface
keeps its jumps and an L2 geometry's its gaps. H1 fields carry
onto the surface's points, interpolated along the same edges; cell
fields (Constant, values on cells) onto its triangles, from the cell
each came from.
Source code in packages/tack-vis/src/tack/dataset/algorithms/contour.py
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 | |
slice ¶
slice(
data,
function,
merge_points=True,
fields=None,
value=0.0,
)
Where function (tack.dataset.implicit) is value (zero by default):
a contour of its values on the geometry, carrying fields as contour
does (merge_points and fields are contour's). value may be
a sequence, as contour's: every level in one
pass, each output cell's in contour_value. slice_plane is this
with a Plane. On a curved geometry (H1 above order 1) the cells are cut
linearized at their corners, as contour would need its cells subdivided
for the curve inside them.
Source code in packages/tack-vis/src/tack/dataset/algorithms/slice.py
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
slice_plane ¶
slice_plane(
data,
origin,
normal,
merge_points=True,
fields=None,
value=0.0,
)
The cut of the 3D cells by the plane through origin with normal (any
length), as triangles: contour at zero of each position's signed distance
to the plane along normal. The distance is computed in the geometry's own
space -- per point, or per cell corner for an L2 geometry -- so the cut is
the cells' own. value, a signed distance along the unit normal, or a
sequence of them, gives parallel planes, all in one pass. slice with a
Plane; merge_points and fields are contour's.
Source code in packages/tack-vis/src/tack/dataset/algorithms/slice.py
33 34 35 36 37 38 39 40 41 42 43 44 45 | |
clip ¶
clip(data, by, value=0.0, inside=False, fields=None)
The part of data where by -- point data (a field or its name) or an
implicit function (tack.dataset.implicit) -- is at or above value, or
with inside below it: Viskores' ClipWithField and (value 0)
ClipWithImplicitFunction, by its case tables. Cells wholly kept keep their
shape; cut cells become the pieces their case lists. Points are the kept
corners, the crossings on cut edges (merged across cells) and centroid
points; point data is interpolated onto them, cell data goes to each piece.
A polyhedral topology is clipped cell by cell into polyhedra: each cut cell
becomes the polyhedron of its kept part, its faces clipped (each face once,
so faces two cells share stay one face) and its iso-polygons -- as
contour traces them, by López's method -- as caps wound out of it; the
output is a PolyhedralTopology. A point at exactly value is kept on
either side (inside keeps at or below). Polygons and polylines are not
clipped yet.
Source code in packages/tack-vis/src/tack/dataset/algorithms/clip.py
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 | |
Surfaces and edges¶
A mesh's faces and edges, as sets or datasets on the same points.
| Algorithm | Gives | Takes | VTK | Viskores |
|---|---|---|---|---|
boundary_faces |
the boundary, as a face set | dataset | — | — |
extract_surface |
faces (the boundary by default) as cells | dataset | vtkDataSetSurfaceFilter | ExternalFaces |
extract_edges |
edges as line cells | dataset | vtkExtractEdges | — |
feature_edges |
a surface's boundary, feature, non-manifold and manifold edges | dataset | vtkFeatureEdges | — |
surface_normals |
cell and point normals of a surface, wound consistently | dataset | vtkPolyDataNormals | SurfaceNormals |
boundary_faces ¶
boundary_faces(data)
The faces with one side -- the boundary -- as a face Set: a 3D mesh's
boundary faces, a 2D mesh's boundary edges, a polyline mesh's free ends,
numbered as data.topology.faces() numbers them. Name it in data.sets
(data.sets["boundary"] = boundary_faces(data)) to iterate over it or
carry it through filters; extract_surface makes a dataset of it.
Source code in packages/tack-vis/src/tack/dataset/algorithms/boundary_faces.py
9 10 11 12 13 14 15 16 | |
extract_surface ¶
extract_surface(data, faces=None, fields=None)
The faces -- a face Set (as boundary_faces returns), a set's name
in data.sets, or face ids (a field or an array); by default the boundary
(boundary_faces), VTK's external faces -- as a dataset on the same points: a 3D
mesh's faces as triangles, quads or polygons, each in its side 0's outward
order; a 2D mesh's edges as line cells; a polyline mesh's ends as vertex
cells. By default a 2D mesh is its own surface: its cells pass through (its
hidden ones left out), as VTK's geometry filter passes polygons; its
boundary edges are extract_surface(data, boundary_faces(data)). Fields on faces become the surface's
cell fields, and so do cell fields (Constant, values on cells), each face
taking its side 0 cell's value; point fields (H1, values on points) stay on
the same points. A 3D mesh's order-2 H1 fields and geometry stay order 2
on the surface, the trace of the solid's: each face's corners, edge middles
and (a quad's) center are the surface cell's, so their values are gathered,
not evaluated. A 2D mesh's boundary lines take an order-2 field's values at
the points. The geometry must be H1: an L2 one has no shared points
for the faces to stand on.
Source code in packages/tack-vis/src/tack/dataset/algorithms/extract_surface.py
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 | |
extract_edges ¶
extract_edges(data, edges=None, fields=None)
The edges -- an edge Set, a set's name in data.sets, or edge ids (a
field or an array); by default every edge of the topology (edges()) --
as a dataset of line cells on the same points, cell i running from edge
i's lower point id to its higher (edges().rows). Values on edges
(Values(data, "edges")) become the output's cell values, and edge sets
its cell sets; point fields (H1, values on points) and point sets stay
on the same points -- an order-2 field's or geometry's values at the
points, the lines being linear. Cell and face fields are left behind: an
edge lies on every cell and face around it. The geometry must be H1:
an L2 one has no shared points for the lines to stand on.
Source code in packages/tack-vis/src/tack/dataset/algorithms/extract_edges.py
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
feature_edges ¶
feature_edges(
data,
angle=30.0,
boundary=True,
feature=True,
non_manifold=True,
manifold=False,
fields=None,
)
A surface's edges of the kinds asked for, as line cells on the same points,
each from its lower point id to its higher, in order of those ids, with
kind (EdgeKind) on the cells -- as VTK's vtkFeatureEdges:
boundary: edges of one cell;feature: edges of two cells whose normals are at leastangledegrees apart (two cells wound opposite ways meet at almost 180);non_manifold: edges of three or more cells;manifold: the other edges of two cells -- all of them when feature edges are not asked for, as in VTK (off by default).
The surface is the 2D cells -- triangles, quads, pixels, polygons; a
polygon's normal is Newell's. Other cells are left out; a volume mesh's
surface comes from extract_surface first. Point fields stay on the same
points (fields chooses which, as extract_edges); cell fields are
left behind, an edge belonging to every cell around it. Hidden cells are
left out. The geometry must be H1.
Source code in packages/tack-vis/src/tack/dataset/algorithms/feature_edges.py
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 | |
EdgeKind ¶
Bases: IntEnum
The values of feature_edges' kind cell field.
surface_normals ¶
surface_normals(data, consistent=True)
A surface -- triangles, quads, pixels or polygons -- with its normals: the
unit normal of each cell (Newell's, round its corners) as the cell field
cell_normals, and at each point the cells' around it summed and made unit
as the point field normals (zero at a point of no cells), as VTK's
vtkPolyDataNormals computes them without splitting.
With consistent, the polygons are first wound alike: each connected
region's as its first (lowest-numbered) cell winds, by union-find over the
edges on the device, the cells that wind against it turned round (their
corners reversed from the first), so their normals agree -- VTK's
consistency, which is on by default there too. A surface no winding makes
consistent (a Moebius strip) is refused, as is an edge of three or more
polygons (the mesh's faces refuse it). A polygonal topology is consistent
already (it refuses to be made otherwise), so it is left as it is. The
result keeps every field; the normals are dimensionless.
Source code in packages/tack-vis/src/tack/dataset/algorithms/surface_normals.py
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | |
Connectivity and winding¶
Which cells belong together, which are wound wrong, and which points are one.
| Algorithm | Gives | Takes | VTK | Viskores |
|---|---|---|---|---|
connected_components |
regions of cells connected through points (or within a radius) | dataset, pieces | vtkConnectivityFilter, vtkEuclideanClusterExtraction | CellSetConnectivity |
check_winding |
cells whose faces do not wind consistently, as a cell set | dataset | — | — |
merge_points |
coincident points (or points within a tolerance) made one | dataset | vtkStaticCleanUnstructuredGrid | CleanGrid |
connected_components ¶
connected_components(
data,
radius=None,
min_size=None,
periodic=None,
locator=None,
)
Which cells are connected, through shared points: data with region, a
field on its cells and one on its points, the regions numbered from 0 in
order of their lowest cell id, as VTK's vtkConnectivityFilter (RegionId,
all regions) and Viskores' CellSetConnectivity: region on the cells,
point_region on the points. A point takes its cells' region; a point no
cell uses, and a hidden cell, has -1. The largest region, or any, is a
threshold of region away.
Computed on the device: union-find over the cells and their points, each cell-point pair hooking the larger root under the smaller with an atomic minimum, then every node jumping to its root, until nothing changes.
Over a PartitionedDataSet, regions are numbered across every piece of
every rank: each piece labels its own, and pieces join the regions that
meet at the points they share. With global ids on
every piece, regions are numbered in order of their lowest global cell id
-- the whole's numbering, for pieces carrying the whole's ids -- else of
their lowest (piece, cell). With radius, the pieces are point clouds:
each labels itself with copies of the other pieces' points within
radius (ghost_points), and a copy joins its region to its owner's.
min_size counts each point once, where it is owned.
With radius, points within that distance of each other (inclusive) are
connected too: on a point cloud, whose cells are its points, that is
Euclidean clustering (VTK's vtkEuclideanClusterExtraction, PCL's) and,
with the linking length, friends-of-friends halo finding (yt's FoF).
min_size gives regions of fewer points -1, the rest numbered again in
the same order. periodic (a box, as PointLocator takes it) joins
points across its faces, as a cosmology box needs. locator reuses a
PointLocator of data.
Source code in packages/tack-vis/src/tack/dataset/algorithms/connected_components.py
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 | |
check_winding ¶
check_winding(data)
The cells whose faces do not wind consistently, as a cell Set.
Walking every face outward (side_point), a closed, consistently wound cell
walks each of its edges exactly twice, once each way. A cell that does not --
a face with the wrong side, an open cell, an edge of three faces -- is listed.
Combinatorial: geometry plays no part. For polygons: the cells whose edges,
walked their way, do not close into a loop. Polylines have no winding, and
shape-based cells take their faces from their shape's table, so are refused.
Source code in packages/tack-vis/src/tack/dataset/algorithms/check_winding.py
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | |
merge_points ¶
merge_points(data, tolerance=0.0, fields=None)
data with each set of coincident points made one: points at most
tolerance apart (inclusive; 0, the default, merges only points of equal
coordinates) join, and so do chains of them, so a set may span more than
tolerance. A set becomes the point of its smallest id, which keeps that
point's position, point fields, ghost marks and global id; the merged points
keep the order of those ids, and the cells are the same cells on them, a
cell's corners that merged kept as they are. Cell fields are unchanged.
For meshes written without merging their points (each piece of a
simulation's output storing a point once for every cell or block that
uses it), which nothing can stitch by matching points across cells or
pieces until each point is one. tolerance is in the dataset's spatial
unit, or a quantity ("1e-9 m"); it should be far below the distance
between points that are not copies, since every point compares itself with
every point that close. Takes an unstructured topology; a structured grid's
points are its lattice, and polyhedral, polygonal and polyline topologies
are not handled yet. Hidden cells are dropped first, as every filter that
makes cells drops them.
Source code in packages/tack-vis/src/tack/dataset/algorithms/merge_points.py
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | |
Changing cell types¶
The same points and region, in other cells.
| Algorithm | Gives | Takes | VTK | Viskores |
|---|---|---|---|---|
tetrahedralize |
3D cells as tetrahedra that meet face to face | dataset | vtkDataSetTriangleFilter | Tetrahedralize |
triangulate |
2D cells as triangles | dataset | vtkTriangleFilter | Triangulate |
as_polyhedra |
a shape-based mesh as a polyhedral one | dataset | — | — |
as_polygons |
2D cells as a polygonal mesh | dataset | — | — |
shrink |
every cell shrunk toward its centroid | dataset | vtkShrinkFilter | Shrink |
tetrahedralize ¶
tetrahedralize(data, fields=None)
Every 3D cell as tetrahedra that meet face to face: two cells sharing a face
split it along one diagonal, as VTK's vtkDataSetTriangleFilter does. A
structured grid's hexahedra take 5 each, the two mirror splits alternating
with the parity of (i, j, k) in the whole grid (the topology's offset
added); any other cell is the cone from its corner of smallest point id --
global id, where data has global ids -- over the faces without it, each
quadrilateral face split from its own smallest corner (a hexahedron or
voxel in 6, a wedge in 3, a pyramid in 2), which fills a convex cell. So
the pieces of a partitioned dataset split the faces they share alike too. Other cells are left out. The
points are the same; cell fields go to each cell's tetrahedra.
Source code in packages/tack-vis/src/tack/dataset/algorithms/tetrahedralize.py
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | |
triangulate ¶
triangulate(data, fields=None)
Every 2D cell as triangles: a quad or pixel in 2, a polygon in a fan from its first point; other cells are left out. The points are the same; cell fields go to each cell's triangles.
Source code in packages/tack-vis/src/tack/dataset/algorithms/triangulate.py
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | |
as_polyhedra ¶
as_polyhedra(data, fields=None)
A shape-based dataset as a polyhedral one, every cell a polyhedron.
Its derived faces already hold every face once, in side 0's outward order
(a voxel's pixel faces as quads), with the side of each cell's reference, so
they become the polyhedral faces as they stand: face ids, and so fields on
faces, are unchanged. Edges come out numbered as before. Cells must be 3D,
and neighbours wound alike: an inside-out cell beside an upright one is
refused, since their shared face would have them on one side.
Constant and H1 order-1 fields, values on points, cells, faces and
edges, and sets carry over; fields needing a reference element are dropped.
Source code in packages/tack-vis/src/tack/dataset/algorithms/as_polyhedra.py
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | |
as_polygons ¶
as_polygons(data, fields=None)
A shape-based dataset of 2D cells -- triangles, quads, pixels -- as a polygonal
one: each cell's points in order become its loop. Fields carry as
as_polyhedra's do, but values on faces and edges, whose numbering the
polygonal topology derives afresh, are dropped.
Source code in packages/tack-vis/src/tack/dataset/algorithms/as_polygons.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | |
shrink ¶
shrink(data, factor=0.5, fields=None)
Every cell shrunk toward its centroid (the mean of its corners) by factor,
as Viskores' Shrink and VTK's vtkShrinkFilter. Viskores gives each cell its
own copies of its points; here that is an L2 geometry on the same
topology, and point data becomes L2 the same way (each cell's own copy
of its corners' values). Everything else is kept, the topology's faces and
edges with it.
Source code in packages/tack-vis/src/tack/dataset/algorithms/shrink.py
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | |
Moving the geometry¶
New positions for the same topology.
| Algorithm | Gives | Takes | VTK | Viskores |
|---|---|---|---|---|
transform |
an affine map (4x4 or 3x4 matrix) | dataset | vtkTransformFilter | PointTransform |
warp |
positions moved along a vector field or vector | dataset | vtkWarpVector, vtkWarpScalar | Warp |
cylindrical |
Cartesian to cylindrical coordinates, or back | dataset | — | CylindricalCoordinateTransform |
spherical |
Cartesian to spherical coordinates, or back | dataset | — | SphericalCoordinateTransform |
transform ¶
transform(data, matrix, fields=None)
The geometry under an affine map: a 4x4 matrix (its last row 0 0 0 1) or 3x4, applied to each position as a column (Viskores' PointTransform, VTK's vtkTransformFilter). On a dataset with an origin the map moves the origin, in double precision, and only its linear part acts on the geometry's values, so a rotation far from zero loses nothing.
Source code in packages/tack-vis/src/tack/dataset/algorithms/transform.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | |
warp ¶
warp(
data, direction, scale=1.0, scale_by=None, fields=None
)
The geometry moved along direction -- a 3-vector field in the geometry's
space (or its name), or one 3-vector for every point -- times scale
and, if given, a scalar field scale_by in the same space (Viskores'
Warp; VTK's vtkWarpVector and vtkWarpScalar).
Source code in packages/tack-vis/src/tack/dataset/algorithms/warp.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
cylindrical ¶
cylindrical(data, inverse=False, fields=None)
The geometry's Cartesian positions as cylindrical (r, theta, z), or back
with inverse -- Viskores' CylindricalCoordinateTransform: theta from
asin(y / r), in [-pi/2, 3pi/2). The positions are the world's
(the origin added), and the result's origin is zero and its spatial unit
unknown, an angle and lengths being no one unit. Fields come along.
Source code in packages/tack-vis/src/tack/dataset/algorithms/cylindrical.py
9 10 11 12 13 14 15 16 | |
spherical ¶
spherical(data, inverse=False, fields=None)
The geometry's Cartesian positions as spherical (r, theta, phi) -- theta
from the z axis, phi in [0, 2pi) -- or back with inverse, as
Viskores' SphericalCoordinateTransform. The positions are the world's (the
origin added), and the result's origin is zero and its spatial unit
unknown, an angle and lengths being no one unit. Fields come along.
Source code in packages/tack-vis/src/tack/dataset/algorithms/spherical.py
9 10 11 12 13 14 15 16 | |
Measures and quality¶
Fields describing the mesh itself.
| Algorithm | Gives | Takes | VTK | Viskores |
|---|---|---|---|---|
cell_geometry |
each cell's volume (area, length) and centroid | dataset | vtkCellSizeFilter | CellMeasures |
face_geometry |
each face's normal and area | dataset | — | — |
cell_centers |
each cell's parametric center (or cell point) | dataset | vtkCellCenters | — |
face_centers |
each face's area centroid | dataset | — | — |
edge_lengths |
each edge's length | dataset | — | — |
cell_quality |
scaled Jacobian, Jacobian ratio, condition number, edge ratio, edge lengths | dataset | vtkMeshQuality | MeshQuality |
point_elevation |
each position along a line, mapped to a range | dataset | vtkElevationFilter | PointElevation |
implicit_values |
an implicit function at the geometry's values | dataset | vtkSampleImplicitFunctionFilter | — |
cell_ids |
each cell's id (provenance through filters) | dataset | vtkGenerateIds | — |
point_ids |
each point's id (provenance through filters) | dataset | vtkGenerateIds | — |
cell_geometry ¶
cell_geometry(data)
CellGeometry(measure, centroid) of each cell: fields on cells. A 3D cell's volume, by the
divergence theorem over its faces -- polyhedral or of a shape -- with faces
taken as fans from their points' means, so a cell with non-planar faces is
measured as that polyhedron (a hexahedron with warped faces is not quite its
trilinear volume); its faces must be wound out of it. A 2D cell's area --
triangle, quad, pixel or polygon -- along its own normal (Newell's), from
its edges going round it. A line's or a polyline's length, and the
centroid of its segments; a vertex's point, with measure 0.
Source code in packages/tack-vis/src/tack/dataset/algorithms/cell_geometry.py
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | |
CellGeometry ¶
Bases: NamedTuple
cell_geometry's fields, on the cells.
face_geometry ¶
face_geometry(data)
FaceGeometry(normal, area): fields on faces. A face's normal points out of
its side 0. A 3D mesh's faces are polygons of any size, the normal the mean
one on a non-planar face; a 2D mesh's faces are edges, the normal in the
plane of side 0's cell and the "area" the edge's length; a polyline mesh's
faces are the polylines' ends, the normal along the line out of side 0 and
the "area" 1.
Source code in packages/tack-vis/src/tack/dataset/algorithms/face_geometry.py
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | |
FaceGeometry ¶
Bases: NamedTuple
face_geometry's fields, on the faces.
cell_centers ¶
cell_centers(data)
Each cell's center, where the geometry maps its parametric center: a field on cells.
That is values_at_centers(data, data.geometry), and not the centroid on a
cell that is not affine; cell_geometry(data).centroid is the centroid, on
every topology, polyhedra included.
A polyhedron, polygon or polyline has no parametric center: its center is its cell point, the mean of its distinct points, where its interpolation fans its faces from. That is not the centroid either, unless the points are spread evenly.
Source code in packages/tack-vis/src/tack/dataset/algorithms/cell_centers.py
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | |
face_centers ¶
face_centers(data)
Each face's area centroid: a field on faces. The face is fanned from its points' mean, so both its cells -- which store it once -- see the same fan. A 2D mesh's face, an edge, has its midpoint, and a polyline's end its point.
Source code in packages/tack-vis/src/tack/dataset/algorithms/face_centers.py
37 38 39 40 41 42 43 44 | |
edge_lengths ¶
edge_lengths(data)
Each edge's length, the distance between its ends in the spatial unit: a
field on edges (Values(data, "edges")), numbered as
data.topology.edges() numbers them. An order-2 geometry's edges are
measured straight. Polylines have no derived edges.
Source code in packages/tack-vis/src/tack/dataset/algorithms/edge_lengths.py
17 18 19 20 21 22 23 24 25 | |
cell_quality ¶
cell_quality(data, measures=None)
How well shaped each cell is: a dict of cell fields, one per measure named in
measures (all of MEASURES by default). Each is 1 on the ideal cell of its
shape (equilateral; a cube, a regular tetrahedron, an equilateral wedge and
pyramid), as Verdict (VTK's vtkMeshQuality) defines it where it does:
scaled_jacobian: the least, over the cell's corners (and a hexahedron's center), of the determinant of its edges there, each scaled to unit length, relative to the ideal cell's: 1 ideal, 0 degenerate, negative inverted.jacobian_ratio: the least corner determinant over the greatest; 1 for a cell whose Jacobian is constant (every simplex), negative where one corner is inverted.condition: the greatest, over the corners, condition number of the corner's edges against the ideal cell's (|T| |T^-1| / d,T = A W^-1, Frobenius norms): 1 ideal, growing as the cell distorts, infinite (the largest float) where a corner is degenerate or inverted.edge_ratio: the longest edge over the shortest.min_edge,max_edge: the shortest and longest edge, in the spatial unit.
The corners' edges are the geometry's Jacobian there applied to the reference cell's edge directions, so an order-2 (curved) cell is measured along its curved edges at its corners. A pyramid, which has no Jacobian at its apex, is measured as Verdict does, by its four tetrahedra over both base diagonals. Polyhedra and polygons, which have no reference cell, are measured by the fan tetrahedra and triangles Tack evaluates them in, each against the regular tetrahedron or equilateral triangle: a valid star-shaped polyhedron has a positive scaled Jacobian, though an ideal-looking one (a cube as a polyhedron) scores below 1. Lines and vertices have no Jacobian measures (NaN); a line's edges are its segments. Hidden cells are measured like the rest.
Matched to VTK 9.7.1's vtkMeshQuality to rounding for the scaled Jacobian
of every linear shape, the condition number of triangles, quads, tetrahedra
and hexahedra, and the edge ratio of triangles, quads, tetrahedra and wedges.
VTK's wedge condition number and hexahedron edge ratio differ (see the
tests).
Source code in packages/tack-vis/src/tack/dataset/algorithms/cell_quality.py
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 | |
point_elevation ¶
point_elevation(
data,
low_point=(0, 0, 0),
high_point=(0, 0, 1),
range=(0.0, 1.0),
)
Each geometry value's position along the line from low_point to
high_point, clamped to it and mapped to range (VTK's
vtkElevationFilter, Viskores' PointElevation): a field in the geometry's
space. The two points are world positions, taken relative to the dataset's
origin in double precision.
Source code in packages/tack-vis/src/tack/dataset/algorithms/point_elevation.py
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | |
implicit_values ¶
implicit_values(data, function)
function, an implicit function (tack.dataset.implicit: Plane,
Sphere, Box, ...), at the geometry's values: a field in the
geometry's space and type, negative inside the function's region. On an
H1 geometry that is one value per point. The function is in world
positions, taken relative to the dataset's origin in double precision.
Source code in packages/tack-vis/src/tack/dataset/algorithms/implicit_values.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | |
cell_ids ¶
cell_ids(data)
Each cell's id, as values on cells: an implicit array, nothing stored.
Stored as a field (data.fields["source"] = cell_ids(data)), it reaches
every filter's output as the id of the input cell each output cell came
from -- kept, a piece of, or a face of -- with no option on the filter.
Source code in packages/tack-vis/src/tack/dataset/algorithms/cell_ids.py
11 12 13 14 15 16 17 18 | |
point_ids ¶
point_ids(data)
Each point's id, as values on points: an implicit array, nothing stored.
Stored as a field, it reaches every filter's output as the id of the input
point each kept point is (cell_ids does the same for cells).
Source code in packages/tack-vis/src/tack/dataset/algorithms/point_ids.py
11 12 13 14 15 16 17 | |
Fields from fields¶
New fields computed from a dataset's fields.
| Algorithm | Gives | Takes | VTK | Viskores |
|---|---|---|---|---|
gradients |
the gradient of a scalar or vector field, at cells or points | dataset | vtkGradientFilter | Gradient |
flow_quantities |
divergence, vorticity and Q-criterion from a velocity gradient | dataset | vtkGradientFilter | Gradient |
project |
a field moved into another space (cells to points, points to cells, ...) | dataset | vtkCellDataToPointData, vtkPointDataToCellData | PointAverage, CellAverage |
values_at_centers |
a field at each cell's parametric center | dataset | — | — |
interpolate |
a @tack.func of position at a space's nodes |
dataset | — | — |
magnitude |
each vector's length | dataset | — | VectorMagnitude |
dot |
the dot product of two vector fields | dataset | — | DotProduct |
cross |
the cross product of two 3-vector fields | dataset | — | CrossProduct |
composite |
scalar fields as the components of a vector field | dataset | — | CompositeVectors |
log_values |
the logarithm of a field, in any base | dataset | — | LogValues |
convert |
a field, or the geometry, in another unit | dataset | — | — |
gradients ¶
gradients(data, field, at='cells')
The gradient of field -- H1, L2 or Constant, scalar or vector --
at each cell's parametric center (at="cells") or at the points
(at="points": each cell's gradient at the point, averaged over the cells
around it, as VTK's vtkGradientFilter and Viskores' point gradients do).
A scalar gives a 3-vector per entity; a vector of k components 3k
values, row by row -- d(u_i)/d(x_j) at 3 * i + j, the layout of
VTK's and Viskores' gradient tensors. The field's basis gives its
derivative in parametric coordinates and the geometry's Jacobian turns that
into a world gradient, so the two need not share a space. A 2D cell's
gradient lies in its plane (VTK's and Viskores' in-plane gradient), lines
and vertices get zero, and a cell of no volume or area NaN (its Jacobian is
singular). flow_quantities derives divergence, vorticity and
the Q-criterion from a vector field's gradient.
On a polyhedral topology there is no reference element. A cell's gradient
of point data is then Green-Gauss over its boundary: the faces walked
outward and fanned from their points' means (the triangles cell_geometry
measures the volume by), each triangle's area vector times the mean of its
values, over the volume. That is exact for a linear field and needs no
interpolant. Cell data, constant in each cell, has a zero gradient there,
as on shapes. A polygon's is the same in its plane, over its edges; polylines
get zero. At the points, the cells' gradients are averaged over the cells
around each point.
Source code in packages/tack-vis/src/tack/dataset/algorithms/gradients.py
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 | |
flow_quantities ¶
flow_quantities(data, gradient)
Divergence, vorticity and the Q-criterion from the gradient of a 3-vector field
(gradients of it: 9 values per entity), in the gradient's space, as VTK's
vtkGradientFilter and Viskores' Gradient compute them, as FlowQuantities:
divergence, vorticity and q_criterion.
Source code in packages/tack-vis/src/tack/dataset/algorithms/flow_quantities.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | |
FlowQuantities ¶
Bases: NamedTuple
flow_quantities' fields, each in the gradient's space.
project ¶
project(data, field, space)
field (a Field or a name) in space, a space of the same topology:
a new field. Values on points or cells count as order-1 H1 and
Constant, both as source and as target.
Into H1, each node takes the mean of the source evaluated there by the
cells around it: the source's own value wherever it is continuous (so a
continuous field of lower order comes across exactly), and an average where
cells disagree -- a cell field's, as vtkCellDataToPointData takes it, or a DG
field's corner values. Into Constant (or order-0 L2), each cell takes
the mean of its corner values, as vtkPointDataToCellData does. Into order-1
L2, each cell takes the source's values at its corners, exactly whatever
the source's order is up to 1. Into SideTraces, each face side the
source's values from its cell (traces). Sums run in a fixed order, so
results do not vary from run to run.
Values on faces or edges have no basis to project, and are refused as a source or target. On polyhedra, polygons and polylines -- no reference element -- only cell values and point values project, one into the other, each cell averaging over its distinct points. The values must be floating point, scalars or vectors.
Source code in packages/tack-vis/src/tack/dataset/algorithms/project.py
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | |
values_at_centers ¶
values_at_centers(data, field)
field evaluated at each cell's parametric center, through its basis.
On a polyhedron, polygon or polyline, which have no reference element, the
center is the cell point (see cell_centers) and point data (H1)
takes its value there, the mean of the cell's distinct points' values: the
value of the cell's interpolant, linear on tetrahedra fanned from that
point. Cell data (Constant) is its own value.
Source code in packages/tack-vis/src/tack/dataset/algorithms/values_at_centers.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | |
interpolate ¶
interpolate(data, function, space, dtype=None, unit=None)
A Field in space whose values are function at each of the space's
nodes: function is a @tack.func of a world position (a 3-vector of
the geometry's type) returning a scalar or a vector, and dtype its
result's type -- data.dtype by default, tack.vec3() for 3-vectors.
The nodes are where the geometry puts them, evaluated as needed and never
stored: H1's points (and at order 2 its edge middles, quad face centers
and cell centers), L2's cell corners (order 1) or parametric centers
(order 0), Constant's and Values(data, "cells")' parametric
centers, Values(data, "points")' points. A node shared by several
cells is evaluated once, by the first cell that has it, so a discontinuous
(L2) geometry gives a shared node the first cell's position. A point no
cell uses takes function at its own position when the geometry is
H1, otherwise zero.
A function of position is what the space can represent exactly when it is
a polynomial of the space's order on straight cells; otherwise this is the
space's nodal interpolant. On polyhedra, polygons and polylines -- no
reference element -- only point values (H1 of order 1 and
Values(data, "points")) of an H1 geometry interpolate.
unit is the values' unit (Field.unit): a function's code does not say
it, so by default it is unknown.
Source code in packages/tack-vis/src/tack/dataset/algorithms/interpolate.py
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | |
magnitude ¶
magnitude(data, field)
Each vector's length (Viskores' VectorMagnitude): a scalar field in the
space and unit of field, a vector field or its name. The values convert
to f32 first, or stay f64, so integer vectors give floating-point lengths.
Source code in packages/tack-vis/src/tack/dataset/algorithms/magnitude.py
18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
dot ¶
dot(data, a, b)
a . b at every value of two vector fields a and b (fields or
names) of one width, dtype and space: a scalar field in that space and
dtype (tack.arrays.Cast converts one first). Its unit is the product of
theirs, by its SI name where it has one ("N" times "m" is "J").
Source code in packages/tack-vis/src/tack/dataset/algorithms/dot.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | |
cross ¶
cross(data, a, b)
a x b at every value of two 3-vector fields a and b (fields
or names) of one dtype and space: a 3-vector field in that space and dtype
(tack.arrays.Cast converts one first). Its unit is the product of
theirs, by its SI name where it has one.
Source code in packages/tack-vis/src/tack/dataset/algorithms/cross.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | |
composite ¶
composite(data, *fields)
Scalar fields of one space as the components of one vector field (Viskores' CompositeVectors), in the order given. The fields must be in one unit, the vector's (an unknown one is not checked).
Source code in packages/tack-vis/src/tack/dataset/algorithms/composite.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | |
log_values ¶
log_values(data, field, base=math.e, min_value=None)
The logarithm of a scalar field in base (e, 2, 10, or any), values below
min_value -- by default the smallest positive normal float, as in
Viskores' LogValues -- taken as min_value. The field must be a pure number
("dimensionless") or of unknown unit: a logarithm of metres is refused.
Source code in packages/tack-vis/src/tack/dataset/algorithms/log_values.py
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | |
convert ¶
convert(data, unit, field=None)
data with field (a name) in unit, or with the geometry in unit
when field is None: a new dataset, the values scaled (and offset,
as from degrees Celsius to kelvin) on the device, the topology, sets, marks
and other fields kept.
The field's unit, or the spatial unit, must be known (Field.unit,
DataSet.unit) and of unit's dimensions. Converting the geometry
scales the origin too, in double precision on the host, so survey
coordinates in US survey feet become metres without losing digits; an
image stays an image and a rectilinear grid keeps its axes. Integer fields
are refused unless the conversion is a relabelling ("1000*m" to
"km").
Source code in packages/tack-vis/src/tack/dataset/algorithms/convert.py
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | |
Finite volumes¶
Values on faces and the cells around them.
| Algorithm | Gives | Takes | VTK | Viskores |
|---|---|---|---|---|
jump |
a field's difference across each face | dataset | — | — |
upwind_flux |
a field's upwind flux through each face | dataset | — | — |
net_flux |
each cell's outward sum of face fluxes | dataset | — | — |
divergence |
each cell's finite-volume divergence of face fluxes | dataset | — | — |
perot |
cell vectors from face-normal components (C-grid models) | dataset | — | — |
jump ¶
jump(data, field)
A field's difference across each face at the face's center, side 1 minus side
0 (zero on the boundary): a field on faces. Each side is the field as its own
cell has it -- traces -- so a DG field's jumps are its own, not its cells'
averages; a continuous field's are zero. Cell data (Constant, values on
cells) is read from each side's cell directly, on any topology, polyhedral
included.
Source code in packages/tack-vis/src/tack/dataset/algorithms/jump.py
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | |
upwind_flux ¶
upwind_flux(data, field, velocity)
The flux of field carried by a constant velocity through each face, out
of side 0: (v . n) * area * u, u taken from the upwind side at the face's
center -- the side the flow leaves -- as a DG advection scheme does. A boundary
face takes its one side's value whichever way the flow goes. divergence of
the result is each cell's net outflow.
Source code in packages/tack-vis/src/tack/dataset/algorithms/upwind_flux.py
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | |
net_flux ¶
net_flux(data, flux)
Each cell's outward sum of flux, values on faces measured out of side 0
(Values(data, "faces", oriented=True), or plain face values taken so): a
face's value counts plus for its side-0 cell and minus for its side-1 cell.
Given each face's integrated flux (upwind_flux, or a normal component times
the area), it is the net outflow of each cell, V times the divergence;
divergence divides by the cells' measure. Nothing is weighted: the
values are summed as they are, so face area vectors sum to zero on a closed
cell. Cells of a lower dimension than the mesh's have no faces: zero.
Source code in packages/tack-vis/src/tack/dataset/algorithms/net_flux.py
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | |
divergence ¶
divergence(data, flux)
Each cell's finite-volume divergence of flux, the integrated flux through
each face out of its side 0 (upwind_flux, or a normal component times the
area): net_flux over the cell's measure (cell_geometry), so for a
linear field it is the field's divergence. A cell of no measure gives no
finite value, as perot and gradients give. flow_quantities gives
the finite-element divergence instead, the trace of a field's gradient.
Source code in packages/tack-vis/src/tack/dataset/algorithms/divergence.py
21 22 23 24 25 26 27 28 29 30 31 32 33 34 | |
perot ¶
perot(data, flux)
A vector per cell from each face's normal component -- Perot's reconstruction,
u_c = (1/V) sum_f A_f u_f (r_f - r_c), as C-grid models (MPAS, ICON) need to
show their face-normal velocity at cells.
flux is a scalar field on faces, the component along each face's normal out
of its side 0; the side each cell is on gives the sign. Face centers, areas
and cell centroids and volumes are this module's, so a uniform field's normal
components give it back exactly on cells with planar faces. First order, as
Perot's method is. A cell of no volume gives NaN, and one of a lower dimension
than the mesh's (no faces of it) zero.
Source code in packages/tack-vis/src/tack/dataset/algorithms/perot.py
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | |
Summaries¶
Python numbers about a field, over every piece and rank.
| Algorithm | Gives | Takes | VTK | Viskores |
|---|---|---|---|---|
statistics |
count, extrema, mean, moments, variance, skewness, kurtosis; weighted | dataset, pieces, AMR, octree | vtkDescriptiveStatistics | Statistics |
entropy |
Shannon entropy of a field's histogram | dataset, pieces, AMR, octree | — | Entropy |
statistics ¶
statistics(data, field, weights=None, level=None)
Descriptive statistics of a scalar field's values, as Viskores' Statistics
filter gives them: a Statistics of n, min, max, sum, mean, the
central moment sums m2, m3, m4, sample_variance
(m2 / (n - 1)), population_variance (m2 / n), their standard
deviations, skewness (sqrt(n) m3 / m2^1.5) and kurtosis
(n m4 / m2^2, not the excess), the last two 0 for a constant field.
Moments are taken about the mean, in a second pass. Ghosts are left out:
cells and points data.ghosts marks a duplicate (another piece owns
them) or hidden.
Over a PartitionedDataSet, field names a field every piece has: each
piece's moments, over what it owns, are taken on its own device and
combined pairwise, in the order of the pieces over every rank of
data.world -- the same result however the pieces are shared out. Where
every shared cell and point has one owner, the result is the whole's up to
rounding. Over an AMRDataSet, the same over every box of every level:
refined cells and points are left out, so each region of space counts
once, at its finest level. Over an OctreeDataSet, its leaves (or the
nodes of one level), every rank's.
weights weighs each value: "volume", each cell's measure (a cell
field's), or a scalar field on the same entities, by name (a density, for
mass-weighted moments). Then mean is sum(w x) / W, sum is sum(w
x), the moments are sum(w (x - mean)^k), weight is W, the sum of
the weights, the population variance m2 / W, the sample variance
m2 / (W - sum(w^2) / W) (reliability weights, NumPy's aweights), and
skewness and kurtosis take W for n. Cells of different sizes -- an
AMR hierarchy's levels, an octree's leaves -- then count by their size.
Source code in packages/tack-vis/src/tack/dataset/algorithms/statistics.py
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | |
Statistics ¶
Bases: NamedTuple
What statistics returns: Python numbers, by name (s.mean), and the
field's unit and the weights' (weight_unit: "dimensionless"
without weights, a measure's unit for "volume"); unit_of(name) gives
each number's.
unit_of ¶
unit_of(name)
The unit of the statistic name ("mean", "sum", ...), or
None if unknown: min, max and mean are in the values'
unit, sum in it times the weights', the moment sums m2 ... m4
in its powers times the weights', the variances in its square and the
standard deviations in it (a difference of degrees Celsius is in
kelvin), skewness, kurtosis and n pure numbers, weight
in the weights' unit.
Source code in packages/tack-vis/src/tack/dataset/algorithms/statistics.py
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | |
entropy ¶
entropy(data, field, bins=10, weights=None, level=None)
The Shannon entropy, in bits, of a scalar field's values in bins equal bins
over their range: -sum(p log2 p) over the bins' shares, as Viskores'
Entropy filter. Ghosts are left out: cells and points data.ghosts
marks a duplicate or hidden.
Over a PartitionedDataSet, field names a field every piece has: the
range is the whole's, and each piece's histogram over what it owns, made on
its own device, is summed, over every rank of data.world. Over an
AMRDataSet, the same over every box of every level, refined cells and
points left out; over an OctreeDataSet, its leaves (or one level's
nodes). With weights ("volume" or a field, as statistics takes
them) each bin's share is the weights' sum in it over theirs.
Source code in packages/tack-vis/src/tack/dataset/algorithms/entropy.py
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | |
Probing, sampling and particles¶
Values at other points, and paths through a velocity field.
| Algorithm | Gives | Takes | VTK | Viskores |
|---|---|---|---|---|
probe |
fields at given points or another dataset's points | dataset, pieces, AMR, octree | vtkProbeFilter, vtkAMRResampleFilter, vtkHyperTreeGridProbeFilter | Probe |
voxelize |
the share of each voxel the data covers, and fields' means over it | dataset, pieces, AMR, octree | — | — |
advect |
particles moved through a velocity field by fixed steps | dataset, pieces | — | ParticleAdvection |
streamlines |
the particles' paths, as polylines | dataset, pieces | vtkStreamTracer | Streamline |
probe ¶
probe(data, where, fields=None, locator=None)
data's fields at the points of where -- a dataset, or an (n, 3)
array of points, which becomes vertex cells -- as Viskores' Probe and VTK's
vtkProbeFilter: the result is where with each field as point data, a
field with a basis (H1, L2, Constant, values on points)
evaluated through it in the cell holding the point, values on cells taken
from that cell, and valid (1, or 0 where no cell holds the point and the
values are 0). locator reuses a CellLocator of data.
data may be an AMRDataSet: each point goes to the finest level whose
cell holds it (refined cells hold nothing), so a probe onto an image
resamples the hierarchy, as VTK's vtkAMRResampleFilter does.
data may be an OctreeDataSet: each point takes the values of the leaf
holding it (cell data, constant over the leaf), found by one binary search
of the keys, as VTK's vtkHyperTreeGridProbeFilter does; a hidden leaf
holds nothing. Over ranks, each point goes to the rank whose trees hold it
and the values come back, as for a PartitionedDataSet.
data may be a PartitionedDataSet: each point goes to the piece one
of whose own cells (not a ghost) holds it, the lowest such piece, which
evaluates it there, and the values come back. where is then this
rank's points -- a dataset, an array, or a PartitionedDataSet, which
gives one back, each piece probed -- and every rank's points are probed
over every rank's pieces of data.world. A point on a face two pieces
share takes the lower piece's values, as one dataset takes its lower
cell's.
Points given as an array are world positions; a dataset's are its own,
relative to its origin. Either way they are taken relative to data's
origin in double precision before they are located (the pieces of a
PartitionedDataSet share one).
Source code in packages/tack-vis/src/tack/dataset/algorithms/probe.py
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | |
voxelize ¶
voxelize(data, grid, fields=None, samples=4)
data on the voxels (cells) of grid, an image (uniform_grid): the
image with fraction, the share of each voxel's volume the data covers, and
each of fields' mean over that share (point and cell fields with a basis,
all by default), as cell data -- what a simulation's fields are on a uniform
grid for analysis or learning. A voxel the data does not reach is hidden
(HIDDENCELL), its means 0.
Each voxel is sampled at samples points along each axis it spans
(samples ** 3 in 3D, ** 2 on a 2D image), evenly, at the centres of
its sub-voxels; a sample is inside where probe finds a cell holding it,
and takes probe's value there. So fraction is right to about one
sample's share, and a mean is the samples' mean, not an integral; more
samples cost more probes. data is anything probe takes: a dataset,
pieces over MPI ranks, an AMR hierarchy (its finest level at each sample)
or an octree. The image keeps its fields; the result is on its device, at
its origin and time. data's and grid's spatial units must agree.
Source code in packages/tack-vis/src/tack/dataset/algorithms/voxelize.py
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | |
advect ¶
advect(
data,
field,
seeds,
step_size,
steps,
integrator="rk4",
locator=None,
)
Seeds moved through the velocity field (a 3-vector field or its name: point
data, or cell data) by up to steps fixed steps of step_size, as
Viskores' ParticleAdvection: a dataset of the particles where they stopped,
one vertex cell each, with steps (taken), time and status (the
bits SUCCESS, TERMINATE, SPATIAL_BOUNDS, TOOK_ANY_STEPS,
ZERO_VELOCITY). seeds is an (n, 3) array or a field of
3-vectors; integrator "rk4" or "euler"; locator reuses a
CellLocator of data.
seeds may also be what advect returned: the particles go on from
where they stopped, with their times and the steps they took (steps
counts those too), and a particle that stopped -- its steps done, at zero
velocity, or outside -- stays as it is. A seed point field on it comes
along.
Over a PartitionedDataSet (field by name), particles cross from
piece to piece, in rounds: each goes to the lowest piece one of whose own
cells (not a ghost) holds it, steps until it leaves that piece, and is
handed on, until none is in flight on any rank.
Each particle is one thread that takes all its steps, so on a GPU a few seeds use a few threads of a device built for thousands, each slower than a CPU core: below a few thousand seeds a CPU copy of the mesh is faster (the Visualization Guide's "Integrate a steady velocity field").
Source code in packages/tack-vis/src/tack/dataset/algorithms/advect.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | |
ParticleStatus ¶
Bases: IntFlag
The bits of advect's status field, Viskores' ParticleStatus:
SUCCESS (cleared when the seed was outside the mesh), TERMINATE (it
stopped after its steps or at zero velocity), SPATIAL_BOUNDS (it left
the mesh), TOOK_ANY_STEPS, ZERO_VELOCITY.
streamlines ¶
streamlines(
data,
field,
seeds,
step_size,
steps,
integrator="rk4",
locator=None,
step_unit="time",
)
The paths of advect's particles, as Viskores' Streamline: each seed's
positions after every step, the seed first, as one polyline per seed
(a PolylineTopology), with seed (which seed's path) on the cells and
time on the points. A seed that takes no step has a point and no line.
The arguments are advect's.
step_unit is what step_size measures: "time" (the default, as
advect and Viskores step), or "length", an arc length along the
path: the particle follows the velocity's direction, as VTK's
vtkStreamTracer does by default, so slow flow takes as long steps as
fast, and steps * step_size is the longest path. time is the time
the particle takes either way.
Over a PartitionedDataSet (field by name), paths cross from piece to
piece, each piece holding the stretches it traced.
Source code in packages/tack-vis/src/tack/dataset/algorithms/streamlines.py
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | |
Point clouds¶
Points as vertex cells: neighbours found through a PointLocator.
| Algorithm | Gives | Takes | VTK | Viskores |
|---|---|---|---|---|
extract_points |
points inside an implicit function | dataset | vtkExtractPoints | ExtractPoints |
threshold_points |
points whose field lies in a range | dataset | vtkThresholdPoints | ThresholdPoints |
mask_points |
every n-th point | dataset | vtkMaskPoints | MaskPoints |
point_normals |
normals from each point's neighbourhood (PCA) | dataset | vtkPCANormalEstimation | — |
outliers |
points marked by a radius or a statistical test | dataset, pieces | vtkRadiusOutlierRemoval, vtkStatisticalOutlierRemoval | — |
voxel_downsample |
one point per occupied voxel | dataset, pieces | vtkVoxelGrid | — |
deposit |
points counted, summed or averaged onto an image (NGP, CIC) | dataset, pieces | — | ParticleDensityNearestGridPoint, ParticleDensityCloudInCell |
sph_density |
SPH densities of particles | dataset | — | — |
sph_interpolate |
particles' fields at other points by SPH kernel sums | dataset | vtkSPHInterpolator | — |
halo_catalog |
a halo catalogue from friends-of-friends labels | dataset, pieces | — | cosmotools |
extract_points ¶
extract_points(data, function, inside=True, fields=None)
The points inside function's region (at or below zero), or outside it
(above), as vertex cells. Needs an H1 geometry: one position per point.
The point twin of extract_geometry: the points as vertex cells, with point fields
carried and cell fields left behind (a vertex cell has no cell of the input).
Source code in packages/tack-vis/src/tack/dataset/algorithms/extract_points.py
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
threshold_points ¶
threshold_points(data, field, lower, upper, fields=None)
The points whose field (point data: H1, values on points) lies in
[lower, upper], as vertex cells.
The point twin of threshold: the points as vertex cells, with point fields
carried and cell fields left behind (a vertex cell has no cell of the input).
Source code in packages/tack-vis/src/tack/dataset/algorithms/threshold_points.py
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | |
mask_points ¶
mask_points(data, stride, fields=None)
Every stride-th point (0, stride, 2 * stride, ...) as vertex cells, as
Viskores' MaskPoints.
The point twin of mask: the points as vertex cells, with point fields
carried and cell fields left behind (a vertex cell has no cell of the input).
Source code in packages/tack-vis/src/tack/dataset/algorithms/mask_points.py
10 11 12 13 14 15 16 17 18 19 20 21 22 | |
point_normals ¶
point_normals(
data,
k=None,
radius=None,
viewpoint=None,
direction=(0.0, 0.0, 1.0),
periodic=None,
locator=None,
)
data with normals, each point's unit normal: the direction in which
its neighbourhood -- its k nearest points (at most 32), itself among
them, or the points within radius -- spreads least, from the
eigenvectors of their covariance, as VTK's vtkPCANormalEstimation and
PDAL's filters.normal. A point with fewer than three neighbours, or
whose neighbours lie on a line, gets a zero normal: so does one whose
neighbourhood's two least spreads (variances) differ by less than
4 sqrt(eps) of the greatest, which the closed-form eigenvalues cannot tell
from a line (in f32, a strip about 4% as wide as it is long).
A normal's sign is chosen to point toward viewpoint (a world position)
when one is given, else along direction (up, by default: the outside
of terrain). periodic (a box) takes neighbours across its faces.
locator reuses a PointLocator of data.
Source code in packages/tack-vis/src/tack/dataset/algorithms/point_normals.py
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | |
outliers ¶
outliers(
data,
radius=None,
min_neighbors=None,
k=None,
deviations=1.0,
periodic=None,
locator=None,
)
data with each point marked an outlier or not: outlier, a u8 point
field, 1 for an outlier. Two tests, as VTK's two filters:
- radius (
radiusandmin_neighbors): fewer thanmin_neighborsother points withinradius, as vtkRadiusOutlierRemoval;neighborsgives each point's count. - statistical (
k, at most 32, anddeviations): a mean distance to itsknearest other points more thandeviationsstandard deviations above the mean of those means, as vtkStatisticalOutlierRemoval and PCL;mean_distancegives each point's. The mean and deviation are taken in double precision on the host.
The points are marked, not removed, as PDAL marks them (class 7):
threshold_points(out, "outlier", 0, 0) keeps the rest. periodic (a
box) measures across its faces. locator reuses a PointLocator of
data.
Over a PartitionedDataSet of point clouds, each piece is searched with
the copies of the other pieces' points it needs (ghost_points: within
radius, or its points' k nearest), and the statistical test's mean
and deviation are over every piece's points: each point gets the whole's
answer. The pieces come back without the copies.
Source code in packages/tack-vis/src/tack/dataset/algorithms/outliers.py
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | |
voxel_downsample ¶
voxel_downsample(data, size, keep='centroid', fields=None)
One point per occupied voxel of a grid of size (a voxel's side, or three
sides, in the geometry's units) anchored at the points' lower corner, as
VTK's vtkVoxelGrid and PDAL's filters.voxeldownsize: vertex cells, with
the input's origin.
keep chooses the point: "centroid", the mean of the voxel's points
(new points: floating-point point fields averaged, integer ones -- a
classification, intensities -- the voxel's first point's); "first",
the voxel's lowest-numbered point; "nearest", its point nearest the
centroid (the lowest-numbered on a tie). The last two keep input points,
in input order, with their fields and ghost marks. Point fields come along
(fields choosing, as filters do); cell fields do not. Each voxel is
reduced in index order, so the result has the same bits from run to run,
and the voxels and kept points are the same on every backend: the
nearest point's distances are rounded alike everywhere
(squared_length), so a tie falls the same way.
Over a PartitionedDataSet, the grid is anchored at the lower corner of
every piece's points, and each voxel's owned points are brought to one
block by a sort over the blocks, in order of piece and then index, and
reduced there. A voxel belongs to the piece of its first point, which keeps
its centroid; "first" and "nearest" keep input points on their own
pieces. Each piece of the result holds its share of the whole's points.
Source code in packages/tack-vis/src/tack/dataset/algorithms/voxel_downsample.py
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | |
deposit ¶
deposit(
data,
grid,
field=None,
reduce=None,
method="nearest",
per_volume=False,
)
data's points deposited on grid, an image (uniform_grid, axis-
aligned): a field on the image.
method="nearest" puts each point in the cell holding it (nearest grid
point, Viskores' ParticleDensityNearestGridPoint): the field is on the
cells. method="cic" spreads it over that cell's corners by trilinear
weights (cloud in cell, Viskores' ParticleDensityCloudInCell, yt): the
field is on the points. Points outside the image are left out.
Without field, each cell's (or corner's) count of points, or sum of
weights. With one -- a point field of data or its name -- reduce
takes "sum" (the default), "mean", "count", or, for
"nearest", "min" and "max": a LiDAR surface model is the
"max" of the heights on a 2D image, a terrain model the "min" of the
ground's. A cell nothing reaches holds 0 for counts and sums, NaN for the
rest. per_volume divides by a cell's volume (area, length on images of
fewer axes): a number or mass density. Each target sums its entries in
point order, so the result has the same bits from run to run. Counts,
minima and maxima are the same on every backend; sums and means agree to
rounding (a GPU may fuse a weight's multiply-add).
data and grid may have different origins: the points are moved to
the image's by the difference, taken in double precision.
Over a PartitionedDataSet, each piece deposits its owned points (not
the copies ghost_points adds) and the pieces' grids combine over every
rank, in order of piece: sums and counts add, means divide the sums by the
counts, minima and maxima take the least and the greatest. Every rank gets
the whole grid's field, on grid's device; field is a name.
Source code in packages/tack-vis/src/tack/dataset/algorithms/deposit.py
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | |
sph_density ¶
sph_density(
particles,
kernel="cubic",
smoothing_length="h",
mass="mass",
dimension=3,
periodic=None,
locator=None,
)
particles with density, each particle's SPH density rho_i = sum_j m_j
W(|x_i - x_j|, h_i) -- itself included, from its own smoothing length (the
gather form, as SPLASH computes it) -- for particles that come without one.
The arguments are sph_interpolate's.
Source code in packages/tack-vis/src/tack/dataset/algorithms/sph.py
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 | |
sph_interpolate ¶
sph_interpolate(
particles,
target,
fields=None,
kernel="cubic",
smoothing_length="h",
mass="mass",
density="density",
normalize=False,
dimension=3,
periodic=None,
locator=None,
)
particles' point fields at target's points by SPH kernel sums: A(x) =
sum_j (m_j / rho_j) A_j W(|x - x_j|, h_j), each particle reaching to its
kernel's support (2, 2.5 or 3 smoothing lengths), as VTK's
vtkSPHInterpolator and SPLASH.
target is a dataset -- an image to resample onto, a slice, a point cloud
-- or an (n, 3) array of world positions, which becomes vertex cells;
the result is target with each field (fields chooses; all point
fields but the smoothing length, mass and density by default) on its points,
and shepard, the sum of the particles' (m / rho) W there: where it
is near 1 the particles cover the point, near 0 they do not. normalize
divides by it (VTK's ShepardProcessing), which makes a constant exact near
the particles' edge.
smoothing_length, mass and density are the particles' point
fields (names or Fields) or numbers for all of them. kernel is
"cubic", "quartic", "quintic" or "wendland", normalized in
dimension dimensions. periodic (a box) reaches across its faces.
locator reuses a PointLocator of the particles. The target's points are moved to the particles' origin by the
difference of the two, in double precision.
Source code in packages/tack-vis/src/tack/dataset/algorithms/sph.py
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | |
halo_catalog ¶
halo_catalog(
particles,
region="point_region",
mass=1.0,
velocity=None,
center="mbp",
softening=0.0,
G=1.0,
periodic=None,
)
The halos of particles -- the groups of region, a point field of
halo labels (connected_components' point_region: friends-of-
friends with radius the linking length; -1 for no halo) -- as a point
cloud of one point per halo, ported from Viskores' cosmotools and ParaView's
LANL halo finder. Each halo's fields:
halo, its label;count;mass, the sum of its particles' (massa point field or one number for all, as Viskores takes it);com, its centre of mass;mbp, its most bound particle (the index of the particle of least potential-G sum m_j / sqrt(r^2 + softening^2)over the halo), andpotential, that least potential;- with
velocity(a 3-vector point field):velocity, the mass- weighted mean, anddispersion, the one-dimensional velocity dispersionsqrt(sum m |v - mean|^2 / 3M); radius, the farthest particle from the halo's point.
The point is the MBP's position (center="mbp") or the centre of mass
("com"). In a periodic box (a side, three, or corners, as
PointLocator takes it) each halo is unwrapped about its first particle
before anything is summed, so a halo across a face is whole, and its points
are put back in the box. Positions are relative to the particles' origin,
which the catalogue shares. Every sum runs in particle order, so the
result has the same bits from run to run; counts and masses are the same
on every backend, the other sums agree to rounding (a GPU may fuse a
multiply-add), and so the most bound particle can differ between
backends only where two potentials tie to rounding.
Over a PartitionedDataSet (labels numbered across the pieces, as
connected_components numbers them), each halo's owned particles are
brought to one block by a sort over the blocks, in order of piece and then
index, and catalogued there: a PartitionedDataSet of catalogues, the
halos in order of label across them, each mbp an index in the piece
mbp_piece (a global piece id).
Source code in packages/tack-vis/src/tack/dataset/algorithms/halo_catalog.py
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 | |
Pieces and ghosts¶
A PartitionedDataSet's pieces, over MPI ranks or not.
| Algorithm | Gives | Takes | VTK | Viskores |
|---|---|---|---|---|
split |
a structured grid cut into pieces, with ghost layers | structured dataset | — | — |
ghost_cells |
pieces with layers of their neighbours' cells | pieces, AMR | vtkGhostCellsGenerator | — |
ghost_points |
point-cloud pieces with copies of their neighbours' points | pieces | — | — |
update_ghosts |
ghost values refreshed from their owners, in place | pieces, AMR | — | — |
remove_ghosts |
a piece without its ghost cells | dataset (a piece) | vtkRemoveGhosts | GhostCellRemove |
generate_global_ids |
points and cells numbered across every piece | pieces | vtkGenerateGlobalIds | — |
merge_pieces |
the pieces as one dataset, on one rank | pieces | vtkMergeBlocks | MergeDataSets |
split ¶
split(data, pieces, ghost_layers=0)
data, a structured grid, cut by index ranges into a PartitionedDataSet
of pieces structured grids, x fastest.
pieces is a count, factored over the axes (each prime factor, largest
first, cuts the axis whose pieces have most cells), or a tuple of counts,
one per axis of the topology. Each axis's cells are shared out as evenly
as they go, every piece getting at least one; neighbouring pieces share
the points between them. A piece keeps the geometry's kind -- uniform
coordinates with their origin moved, rectilinear axes cut, explicit
points taken -- and carries its block of every point, cell and L2 field
and every set, as filters carry them; fields on faces or edges are left
behind.
ghost_layers adds that many layers of each neighbour's cells around a
piece, as far as the grid goes, marked duplicates (DUPLICATECELL in
ghosts), as VTK's vtkGhostCellsGenerator adds them. Filters need
one to give each piece's share of what they make on the faces and edges
pieces share. Every point has one owner: a point of several pieces' own
cells is the first such piece's, x fastest, and a duplicate
(DUPLICATEPOINT) in the others, as is every point of the ghost
layers. The input's own ghost marks come along. An unstructured mesh is
partitioned by VTK (vtkRedistributeDataSetFilter) or by its host
application.
Source code in packages/tack-vis/src/tack/dataset/algorithms/split.py
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | |
ghost_cells ¶
ghost_cells(
data, layers=1, world=None, fields=None, tolerance=0.0
)
data's pieces, each with a layer of its neighbours' cells added as ghosts:
a new PartitionedDataSet, as VTK's vtkGhostCellsGenerator makes.
data holds this rank's pieces, all unstructured
(UnstructuredTopology) or all structured (below), without ghost cells,
on any devices; world is the
tack.communication.World they live in (data.world, this process
alone, by default). Pieces neighbour where they share points at exactly
the same position, or with tolerance at most that far apart (in the
spatial unit, or a quantity; chains of them too), for pieces whose copies
of a shared point differ in their last bits. Each piece gains the cells of other pieces that use a
point it shares with them (DUPLICATECELL), and their points it lacks
(DUPLICATEPOINT); a point several pieces share belongs to the lowest
of them, and is DUPLICATEPOINT in the others. Input marks come along:
HIDDENCELL and the other bits are copied with each cell.
Point and cell fields (H1 of order 1, Constant, values on points
or cells) are carried, fields choosing them as filters do (None:
all); point and cell sets keep their members, and global ids come along
where every piece has them, points then matched by their ids rather than
their positions. update_ghosts refreshes
a field's ghost values from their owners afterwards, after a filter that
is right only where it sees every neighbour.
layers layers: the first, every filter's need, is the cells that use
a point a piece shares; each further layer the cells, of any piece, that use
a point of the layer before (a corner's diagonal neighbours included), as
split's ghost layers and vtkGhostCellsGenerator's grow. Pieces
thinner than layers cells are fine: a layer reaches past them to the
pieces beyond.
Structured pieces (StructuredTopology, each placed by its offset,
together tiling a box of cells, as split's and VTK's image pieces do)
stay structured: each becomes its block grown by layers cells, as far
as the box goes, with its values from the pieces that own them, and its
implicit coordinates grown with it.
An AMRDataSet's boxes each grow by layers cells within their level's
domain, the ghosts marked DUPLICATECELL and DUPLICATEPOINT: a ghost a
sibling box holds is its value, copied (AMReX's FillBoundary); one no
sibling holds takes the coarser level's -- the coarse cell's value for a
cell, the coarse cell's corners' by linear weights for a point
(FillPatchTwoLevels) -- and one neither holds is hidden. A grid's ghost
positions are filled the same way, so rectilinear and curvilinear boxes
grow too. Ghosts a finer box covers are marked refined. update_ghosts
refreshes them, average_down puts fine data into coarse cells.
Source code in packages/tack-vis/src/tack/dataset/algorithms/ghost_cells.py
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | |
ghost_points ¶
ghost_points(
data,
radius=None,
k=None,
periodic=None,
around=None,
fields=None,
world=None,
)
data's pieces, point clouds (point_cloud: one vertex cell per point),
each with copies of the other pieces' points it needs appended as ghosts: a
new PartitionedDataSet.
With radius, every point within radius of the piece's box: a filter
over the points within radius (outliers' radius test,
point_normals, sph_density, sph_interpolate with its kernel's
reach) then gives each owned point the whole's answer. around (a
PartitionedDataSet with a piece for each of data's) widens each
piece's box to its own piece's points too: the targets sph_interpolate
resamples onto there. With k (at most 32), every point among an owned
point's k nearest, wherever it is: point_normals and outliers
by the k nearest then see the whole's neighbours.
Each copy is a vertex cell marked DUPLICATECELL at a point marked
DUPLICATEPOINT, owned by the piece it came from, after the piece's own
points, which keep their order. Point and cell fields come along
(fields choosing, as filters do), sets keep their members, and global
ids come along where every piece has them. update_ghosts refreshes a
field's copies from their owners, after a per-piece filter that is right
only on what a piece owns (sph_density before sph_interpolate).
In a periodic box (as PointLocator takes it) a copy across a face is
an image, moved by the box's side: run filters on the result without
periodic. data holds this rank's pieces, on any devices, in
world (data.world by default), on one origin, without ghosts.
Source code in packages/tack-vis/src/tack/dataset/algorithms/ghost_points.py
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | |
update_ghosts ¶
update_ghosts(data, *fields)
Copy the named fields' values on ghost points and cells from the pieces that
own them, in place, for a PartitionedDataSet that ghost_cells or
ghost_points made.
A filter that needs each entity's neighbours -- project to points, point
gradients -- is right on what a piece owns and wrong on the ghost layer's
rim; refreshing the ghosts makes a second such filter right too. Each named
field is a point or cell field of explicit values on every piece. One
exchange moves all the point fields, one all the cell fields.
Source code in packages/tack-vis/src/tack/dataset/algorithms/ghost_cells.py
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | |
remove_ghosts ¶
remove_ghosts(data, fields=None)
data without its ghost cells -- the cells another piece owns
(DUPLICATECELL) and the hidden ones (HIDDENCELL) -- and the points no
remaining cell uses, as VTK's vtkRemoveGhosts: the last step before
rendering, writing one file, or handing a piece to code that does not know
ghost marks. Filters keep the ghost layers' output, which the next filter
near a piece's edge needs, until this.
The points kept keep their marks: a point a piece shares with another is
still that one's, so reductions still count it once. A point this piece
owns stays even when only ghost cells used it (a point a filter made on an
edge goes to the piece owning the edge's smallest point, whose cells there
may all be ghosts), so every point keeps its one owner. A structured grid
whose remaining cells are a block stays a structured grid; otherwise the
cells are kept as extract_cells keeps them. data itself comes back
when it has no ghost cells.
Source code in packages/tack-vis/src/tack/dataset/algorithms/remove_ghosts.py
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | |
generate_global_ids ¶
generate_global_ids(data, world=None, tolerance=0.0)
data's pieces numbered across every piece, as VTK's
vtkGenerateGlobalIds: a new PartitionedDataSet of the same pieces with
global_ids (tack.dataset.GlobalIds), every point and cell one id,
the same on every piece holding it.
Each piece numbers what it owns in its own order, after the pieces before
it -- piece 0's owned points from 0, then piece 1's -- and copies take their
owner's ids. Owners come from the ghost marks where pieces have them (a
copy marked DUPLICATEPOINT or DUPLICATECELL is another piece's);
a point several pieces hold unmarked, as pieces without ghost cells share
their boundary points, belongs to the lowest of them, and is marked
DUPLICATEPOINT in the others, as VTK marks it. Points are copies of
each other when their positions are exactly equal -- or, with
tolerance, at most that far apart (in the spatial unit, or a
quantity), chains of them too, for pieces whose copies of a shared point
differ in their last bits -- and cells when their points' global ids are;
pieces from ghost_cells reuse the exchanges it kept instead of
matching again. Points a piece repeats within itself stay apart:
merge_points merges them first.
data holds this rank's pieces, on any devices; world is the
tack.communication.World they live in (data.world by default).
The matching runs on the host and the exchanges on the pieces' devices.
Source code in packages/tack-vis/src/tack/dataset/algorithms/generate_global_ids.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | |
merge_pieces ¶
merge_pieces(
data, root=0, fields=None, device=None, tolerance=0.0
)
data's pieces as one dataset: every cell a piece owns, once, and every point
once, with the point and cell fields (fields choosing them as filters
do), as VTK's vtkMergeBlocks after vtkRemoveGhosts.
Copies of a point several pieces hold become one point, matched as
generate_global_ids matches them (the ghost marks' owners, else exact
positions, or positions at most tolerance apart); ghost cells are left out, and hidden cells kept, marked. The
points and cells are numbered as generate_global_ids numbers them:
each piece's owned ones after the pieces before it. The pieces' own global
ids, if they have them, come along as the result's.
Structured pieces that tile a box of cells (each placed by its
topology.offset) give one structured grid of the box -- split's
input back -- with its offset and its coordinates (uniform, rectilinear
or explicit); other pieces give an unstructured dataset.
Across MPI ranks every rank passes its pieces and the result is on the
root rank (None elsewhere), on device (by default the device
of the root rank's first piece).
Source code in packages/tack-vis/src/tack/dataset/algorithms/merge_pieces.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | |
Adaptive mesh refinement¶
Algorithms only an AMRDataSet takes; ghost_cells, probe, statistics and the rest above take one too.
| Algorithm | Gives | Takes | VTK | Viskores |
|---|---|---|---|---|
average_down |
finer levels' data averaged into the coarse cells they cover, in place | AMR | — | — |
average_down ¶
average_down(amr, field)
Put the finer levels' data into the coarse cells and points they cover, in
place, as AMReX's average_down: each refined coarse cell (REFINEDCELL)
takes the volume-weighted average of the fine cells over it, each refined
coarse point (REFINEDPOINT) the value of the fine point at its location.
field names a floating-point point or cell field every box has; finest
level first, so a value reaches the coarsest level in one call.
Source code in packages/tack-vis/src/tack/dataset/algorithms/amr_ghosts.py
484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 | |