Data Model¶
The classes and constructors of tack.dataset, and the interop with VTK,
MFEM and Warp, with their parameters, methods and properties. The
Dataset API reference explains how they fit together; the
algorithms that work on them are in Algorithms.
Contents¶
- Datasets and fields:
DataSet,Field, the constructors and sources - Data in pieces and hierarchies:
PartitionedDataSet,AMRDataSet,OctreeDataSet - Topologies: structured, unstructured, polyhedral, polygonal, polyline; cell shapes
- Spaces: where a field's values live
- Sets, ghost marks and global ids
- Implicit functions
- Locating points:
CellLocator,PointLocator - Steps in time: temporal filters and particle paths
- Kernels over meshes:
for_each - Filters of your own:
carryand its maps, ghost marks, hidden cells, owned values - Units: parameters as quantities, units of what a filter computes
- Interoperability: VTK, MFEM, Warp
Datasets and fields¶
tack.dataset.DataSet ¶
DataSet(
topology,
geometry,
fields=None,
sets=None,
dtype=tack.f32,
ghosts=None,
global_ids=None,
origin=None,
unit=None,
time=None,
time_unit=None,
)
A topology, a geometry, named fields and named sets. The geometry is a
Field in H1 or L2 of 3-vectors -- or positions per point (a host
array, or any array of 3-vectors, such as a device array or a
CartesianProduct), which become an H1 field -- kept apart from
fields as data.geometry: algorithms move it with the cells, never as
one of the fields they carry or select. ghosts, a
tack.dataset.Ghosts or None, marks the cells and points
another piece of a partitioned dataset owns; global_ids, a
tack.dataset.GlobalIds or None, numbers them across the pieces.
origin, three floats kept in double precision on the host, is where the
geometry's coordinates start: a position is origin + value, the value
in the geometry's type (tack.f32, or tack.f64 on a device that has
it). Kernels see only the values, so coordinates far from zero -- a LiDAR
survey's eastings and northings -- keep their precision in single
precision. Filters keep it; what takes or gives absolute positions
(implicit functions, probe points, seeds, world_positions, the VTK
interop) converts on the host in double precision.
unit is the spatial unit, the geometry field's (DataSet.unit), in
which origin is too; a geometry Field brings its own.
time is when the data are, a double on the host or None, in
time_unit (a unit of time, or None for unknown): one step of a
simulation as it streams through filters, which keep both, as they keep
the origin. Tack holds no series of steps; filters that need earlier steps
keep what they need (tack.dataset.temporal).
Source code in packages/tack-vis/src/tack/dataset/dataset.py
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 | |
origin
property
writable
¶
origin
Where the geometry's coordinates start, (x, y, z) in double precision:
a position is origin plus the geometry's value. (0.0, 0.0, 0.0)
unless set.
sets
property
writable
¶
sets
Named Sets of this dataset's points, cells, faces or edges, checked as
they are put in: a set of another topology, or named for a domain, is
refused.
ghosts
property
writable
¶
ghosts
The ghost marks of its cells and points (tack.dataset.Ghosts),
or None: which cells and points another piece owns, in VTK's
vtkGhostType bits.
Filters that keep the cells and points (warp, transform,
shrink, as_polyhedra, probe's target, ...) keep the marks;
others do not carry them yet, and their output remembers that it lost
them, so statistics and entropy refuse it rather than count
shared cells and points once per piece. Setting marks again clears
that.
global_ids
property
writable
¶
global_ids
The global ids of its points and cells (tack.dataset.GlobalIds), or
None: each one's number across every piece of a partitioned dataset.
Filters carry them where an output entity is an input entity (kept points,
kept or extracted cells); what a filter makes has none.
geometry
property
writable
¶
geometry
The geometry, a field of positions in H1 or L2, relative to
origin.
unit
property
¶
unit
The spatial unit, the geometry's (origin is in it too), a string, or
None if unknown.
to_device ¶
to_device(device)
This dataset on device: itself if it is already there, else a copy --
the topology made from its arrays copied there, each field in the same
space on it, the sets by the same ids. What the topology derives (faces,
groups, layouts) is derived there afresh, not copied.
Source code in packages/tack-vis/src/tack/dataset/dataset.py
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 | |
positions ¶
positions()
Every point's position as a host array, (num_points, 3): an H1
geometry's values, relative to origin (world_positions adds it).
An L2 geometry has none per point.
Source code in packages/tack-vis/src/tack/dataset/dataset.py
478 479 480 481 482 483 484 485 | |
rebased ¶
rebased(origin)
This dataset with its geometry relative to origin instead: the same
world positions, each value moved by the difference of the two origins
(taken in double precision, added in the geometry's type). An image
stays an image, its corner moved in double precision, so an image at
survey coordinates rebased to a nearby origin holds its points exactly;
a rectilinear grid moves its axes. Other implicit geometry becomes
positions per point.
Source code in packages/tack-vis/src/tack/dataset/dataset.py
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 | |
world_positions ¶
world_positions()
Every point's position as a float64 host array, (num_points, 3):
origin plus positions(), added in double precision.
Source code in packages/tack-vis/src/tack/dataset/dataset.py
518 519 520 521 | |
domain_groups ¶
domain_groups(domain)
The DomainGroups of domain: "cells", "faces", "edges", or
the name of a cell, face or edge set in sets -- a cell set's as the
cell groups' subgroups of its members.
Source code in packages/tack-vis/src/tack/dataset/dataset.py
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 | |
domain_view ¶
domain_view(domain, group, extra=(), **extra_attributes)
group's view: kind, shape, geometry, and for cells their incidence when
faces and edges have been derived; extra mixins and attributes last
(a space's node mixins, node_views).
Source code in packages/tack-vis/src/tack/dataset/dataset.py
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 | |
node_views ¶
node_views(space)
Views over space's nodes, each visited once: entity views -- points,
edges, quad faces, cells -- whose node_count(i) nodes have
node_dof(i, j), where the node's value is in space's values, and
node_position(i, j), where the geometry puts it. for_each(kernel,
data, space, ...) runs a kernel over them:
for i in nodes:
for j in range(nodes.node_count(i)):
values[nodes.node_dof(i, j)] = f(nodes.node_position(i, j))
H1's nodes are its points and, at order 2, its edges' middles, its
quad faces' centers and its hexahedra's and quads' interiors; a shared
node's position is the mean of its entity's points on a straight
geometry, the geometry's own value there on an order-2 one. L2's
are its cells' corners (order 1) or parametric centers (order 0),
Constant's and values on cells' the parametric centers, values on
points' the points. Shared nodes need positions per point: an L2
geometry has none, and is refused for them. On polyhedra, polygons and
polylines only point nodes exist.
Source code in packages/tack-vis/src/tack/dataset/dataset.py
591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 | |
launch_groups ¶
launch_groups(domain, fields=(), keys=())
The groups a kernel over domain launches once each, given the Fields
it reads: the domain's groups, each split by the keys of the spaces among
fields that vary from cell to cell, and by any other keys -- objects
that give each cell a key, as polyhedra.SizeBuckets does.
Source code in packages/tack-vis/src/tack/dataset/dataset.py
682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 | |
tack.dataset.Field ¶
Field(space, values, unit=None)
A space and its values: any array, explicit or implicit, of scalars or vectors, with one value per degree of freedom of the space. Everything else a kernel needs to find them belongs to the space.
unit is the values' physical unit, a string such as "m/s" kept in
unyt's canonical spelling (tack.dataset.units), or None for unknown:
meaning, which kernels never see. Filters that keep a quantity keep its
unit; anything else gives None rather than a guess.
Source code in packages/tack-vis/src/tack/dataset/dataset.py
54 55 56 57 58 59 60 61 62 63 64 | |
view ¶
view(group)
This field's view for group: the group's kind and shape, the space, and
the storage its values are read through. A space that varies reads its key
for the group from group.keys: launch it through for_each.
Source code in packages/tack-vis/src/tack/dataset/dataset.py
83 84 85 86 87 88 | |
Making datasets¶
tack.dataset.uniform_grid ¶
uniform_grid(
dims,
origin=(0.0, 0.0, 0.0),
spacing=(1.0, 1.0, 1.0),
direction=None,
dtype=tack.f32,
device=None,
unit=None,
)
A dataset of dims points (one to three sizes) at origin + direction @
(ijk * spacing): VTK's image data, Viskores' uniform data set. Nothing per
point is stored (UniformCoordinates). A size of 1 adds no cells, in any
position, and a grid with no axis of two points has none (grid_topology).
origin and spacing take a value per size given (or three), and
direction is 3 x 3 (2 x 2 for a 2D image). It is on device (by
default the default device).
The image's coordinates are in the geometry's type, so one far from zero
-- a raster at survey coordinates -- loses digits in single precision:
rebased(origin) keeps it an image with its corner relative to a nearby
dataset origin, moved in double precision, as a point cloud's origin
keeps its points. unit is the spatial unit (DataSet.unit), which
origin and spacing are in.
Source code in packages/tack-vis/src/tack/dataset/dataset.py
884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 | |
tack.dataset.rectilinear_grid ¶
rectilinear_grid(
x,
y=(0.0,),
z=(0.0,),
dtype=tack.f32,
device=None,
unit=None,
)
A dataset of the grid whose lines run through x, y and z; see
grid_topology for axes of one point or none. It is on device, by
default that of axes given as device arrays, else the default device.
unit is the spatial unit (DataSet.unit).
Source code in packages/tack-vis/src/tack/dataset/dataset.py
874 875 876 877 878 879 880 881 | |
tack.dataset.point_cloud ¶
point_cloud(
positions,
fields=None,
origin=None,
dtype=tack.f32,
device=None,
unit=None,
units=None,
)
A point cloud: a dataset of positions, one vertex cell per point (types,
offsets and connectivity implicit, stored nowhere), with fields (a dict of
names to one value per point, scalars or rows of components) as point data.
positions is an (n, 3) host array of world positions, or a Tack array
of 3-vectors already relative to origin. Host positions are taken
relative to origin in double precision before they become dtype;
without one, float64 positions get the lower corner of their box, rounded
down to whole units, so a LiDAR survey's eastings and northings keep their
precision in single precision, and other positions get zero. dtype is
tack.f32, or tack.f64 on a device that has it. Floating-point fields
become dtype too; integer fields (a LAS classification, intensities) keep
their type. unit is the spatial unit (DataSet.unit), which origin
is in, and units maps field names to their units.
Source code in packages/tack-vis/src/tack/dataset/dataset.py
905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 | |
Sources¶
tack.dataset.sources ¶
Datasets made from formulas, for tests and examples: Viskores' sources
(viskores::source), on rectilinear grids with uniform spacing.
wavelet is VTK's and Viskores' wavelet (vtkRTAnalyticSource): a
Gaussian plus three periodic terms, the point field RTData. tangle
is the tangle cube, a quartic with a surface of several handles at 0.5, the
point field tangle.
tack.dataset.wavelet ¶
wavelet(
extent=10,
spacing=1.0,
center=(0.0, 0.0, 0.0),
maximum=255.0,
frequency=(60.0, 30.0, 40.0),
magnitude=(10.0, 18.0, 5.0),
standard_deviation=0.5,
dtype=tack.f32,
device=None,
)
VTK's and Viskores' wavelet on the grid of point indices extent (n:
-n to n on every axis, or x0, x1, y0, y1, z0, z1), points at index times
spacing: maximum * exp(-|a|^2 / (2 sd^2)) plus magnitude times
sin, sin and cos of frequency * a, where a is center minus the
point, each axis divided by its extent's length. It is on device (by
default the default device).
Source code in packages/tack-vis/src/tack/dataset/sources.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | |
tack.dataset.tangle ¶
tangle(dims=(16, 16, 16), dtype=tack.f32, device=None)
Viskores' tangle on dims points per axis over [0, 1]^3: the quartic
(x^4 - 5x^2 + y^4 - 5y^2 + z^4 - 5z^2 + 11.8) / 5 + 1/2 of the point
mapped to [-3, 3]^3, the point field tangle. It is on device (by
default the default device).
Source code in packages/tack-vis/src/tack/dataset/sources.py
83 84 85 86 87 88 89 90 91 92 93 94 95 96 | |
tack.dataset.amr_gaussian_pulse ¶
amr_gaussian_pulse(
dimension=3,
root_spacing=0.5,
refinement_ratio=2,
pulse_origin=(0.0, 0.0, 0.0),
pulse_width=(0.5, 0.5, 0.5),
amplitude=0.0001,
dtype=tack.f32,
device=None,
)
A two-level AMR hierarchy with a Gaussian pulse, as VTK's
vtkAMRGaussianPulseSource makes it: a root image of 6 x 6 (x 6) points from
(-2, -2, 0), and two boxes refining parts of it by refinement_ratio.
Each box has VTK's two cell fields: Gaussian-Pulse, amplitude *
exp(-sum(((c - pulse_origin) / pulse_width)^2)) over the first
dimension axes at each cell's centre c, and Centroid, the
centres. An AMRDataSet, its refined cells and points marked.
Source code in packages/tack-vis/src/tack/dataset/sources.py
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 | |
Data in pieces and hierarchies¶
tack.dataset.PartitionedDataSet ¶
PartitionedDataSet(partitions=(), world=None)
A dataset in pieces: an ordered list of DataSets (partitions), each
with its own topology, fields, sets and device, as VTK's
vtkPartitionedDataSet. A piece may have no cells. Pieces may be on
different devices; an algorithm given the partitioned dataset runs on each
piece where the piece is.
It is a container, not a dataset: iterate over it (for piece in pds),
index it (pds[i]) or take its length.
Across MPI ranks, it holds this rank's pieces, and world (a
tack.communication.World; this process alone by default) is where the
others are, for the algorithms that combine pieces.
Source code in packages/tack-vis/src/tack/dataset/partitioned.py
37 38 39 40 41 42 43 44 45 46 47 | |
tack.dataset.AMRDataSet ¶
AMRDataSet(levels, ratios=(), domain=None, world=None)
Levels of structured boxes, each level finer than the one before: a
block-structured AMR hierarchy, as VTK's vtkOverlappingAMR and AMReX's.
levels holds this rank's boxes of each level, a PartitionedDataSet or a
list of DataSets each. Every box has a StructuredTopology (all of one
dimension) whose offset is its first cell's index in its level, and any
structured geometry: an image, a rectilinear grid or a curvilinear grid.
ratios[l] refines level l into l + 1: a whole number, or one per
axis. domain is level 0's cell-index box (lo, hi), hi exclusive;
by default its boxes' bounding box. world is where the other ranks' boxes
are (tack.communication.World; the levels' own, else this process).
Refused when made: boxes of a level that overlap; a box of level l + 1
that, coarsened, is not within level l's boxes (proper nesting); image
boxes of a level not on one lattice (one spacing, one origin, axis-aligned:
VTK's model); boxes with ghost cells (DUPLICATECELL).
The boxes it holds are copies of those given with their marks computed
(REFINEDCELL, REFINEDPOINT, siblings' DUPLICATEPOINT; other bits
kept), sharing their topology, geometry and fields. Iterating over it gives
(level, box) for this rank's boxes, coarsest level first.
Source code in packages/tack-vis/src/tack/dataset/amr.py
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 | |
level_domain ¶
level_domain(level)
domain refined to level: its cell-index box there.
Source code in packages/tack-vis/src/tack/dataset/amr.py
109 110 111 112 113 114 115 | |
boxes ¶
boxes(level)
Every rank's boxes of level, an (n, 2, 3) integer array of cell-index
boxes [lo, hi), in order of global index: rank 0's first, each rank's
in the order given. Axes a box does not have are [0, 1).
Source code in packages/tack-vis/src/tack/dataset/amr.py
144 145 146 147 148 | |
tack.dataset.OctreeDataSet ¶
OctreeDataSet(
roots,
tree,
level,
key,
fields=None,
mask=None,
world=None,
units=None,
)
A grid of octrees, Morton-sorted: VTK's vtkHyperTreeGrid with branching
factor 2, as a linear octree.
roots is the grid of root cells, a dataset of a StructuredTopology
over an image's or a rectilinear grid's coordinates, 2D or 3D
(uniform_grid, rectilinear_grid); its cell t (x fastest) is
tree t. tree, level and key give each node: its tree, its
level (0 at the root) and its Morton key at that level (integer coordinates
in the tree interleaved, x lowest). Every node's ancestors must be among
the nodes. fields maps names to one value per node (cell data), in the
order the nodes are given; mask (one per node, nonzero hides) becomes
HIDDENCELL. The nodes are sorted in pre-order and the fields with them.
world is where other ranks' trees are, for reductions: each rank holds
whole trees. units maps field names to their units
(tack.dataset.units); the spatial unit is the roots' (unit).
OctreeDataSet.from_cells makes one from cells' levels and positions, as
a RAMSES reader gives them; tack.interop.vtk converts
vtkHyperTreeGrids.
Source code in packages/tack-vis/src/tack/dataset/octree.py
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 | |
geometry ¶
geometry()
Each node's lower corner and sides, relative to origin: two arrays of
3-vectors (f64 where the device has it, else the roots' type), from the
keys, made once.
Source code in packages/tack-vis/src/tack/dataset/octree.py
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 | |
centers ¶
centers()
Each node's centre, relative to origin: a host (n, 3) array.
Source code in packages/tack-vis/src/tack/dataset/octree.py
375 376 377 378 | |
measures ¶
measures()
Each node's volume (area in 2D): a host array.
Source code in packages/tack-vis/src/tack/dataset/octree.py
380 381 382 383 384 | |
leaves ¶
leaves()
This rank's leaves as a DataSet of voxels (pixels in 2D), relative to
origin: cell c is the c-th leaf in the nodes' order
(np.flatnonzero(octree.is_leaf)[c]), with every field's value there as
cell data, and hidden leaves HIDDENCELL. Corners that coincide are one
point, found by sorting their indices on the finest level's lattice;
hanging corners stay hanging, as the tree has them. Every filter that
reads cell data works on it (threshold, cell_centers, probe).
Each rank gives its own trees' leaves; a corner on a face between two ranks' trees is a point on both, unmarked.
Source code in packages/tack-vis/src/tack/dataset/octree.py
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 | |
from_cells
classmethod
¶
from_cells(
roots,
level,
position,
fields=None,
world=None,
units=None,
)
An octree of cells given by their level and position (an (n, 3)
world position inside each cell, its centre say), as a RAMSES reader or
yt gives them -- whole octs: every refined cell's children. Each cell's
tree is the root cell holding the position; its key, the integer
coordinates there at its level. Ancestors no cell gives are added, their
fields NaN (integer fields 0): reductions of the leaves never read
them.
Source code in packages/tack-vis/src/tack/dataset/octree.py
587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 | |
Topologies¶
tack.dataset.StructuredTopology ¶
StructuredTopology(
point_dims, id_dtype=None, device=None, offset=None
)
Bases: _Topology
The cells of a structured grid of point_dims points, x fastest: lines, quads
or hexahedra in vtkStructuredGrid's order, iterated by (i, j, k).
offset is the grid index of its first point in a larger grid, one per
axis (VTK's extent's start; zeros by default): a piece split cut, or an
image whose extent starts elsewhere. What depends on a cell's place in the
whole grid -- tetrahedralize's alternating splits -- reads it, so
neighbouring pieces agree.
Source code in packages/tack-vis/src/tack/dataset/topology.py
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 | |
tack.dataset.UnstructuredTopology ¶
UnstructuredTopology(
types,
offsets,
connectivity,
num_points=None,
id_dtype=None,
)
Bases: _Topology
Cells of any of the linear shapes: a VTK type per cell (u8), and each
cell's point ids at connectivity[offsets[c]:offsets[c + 1]], over
num_points points -- by default one past the highest id used, but a
dataset may have points no cell uses, as VTK's may.
offsets and connectivity are fields or arrays of ids, kept in
id_dtype: i32 unless asked for tack.i64, given i64 fields,
or too large for i32 -- a mesh whose ids, connectivity or derived
arrays (twelve edges a cell) pass 2**31 - 1.
Any of the three may be an implicit array (tack.arrays), kept as it is:
the types of cells all of one shape a ConstantArray, the offsets of
cells all of one size a CountingArray (uniform_cells makes both),
the connectivity of cells that each have their own points a
CountingArray too. VTK's constant and affine arrays map onto them.
Source code in packages/tack-vis/src/tack/dataset/topology.py
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 | |
tack.dataset.polyhedra ¶
Polyhedral topologies: every cell a list of faces, every face a polygon stored once.
docs/vis-guide/design/polyhedra.md, phase 1. A PolyhedralTopology holds
face_offsets/face_points: each face's points (CSR), in the order that winds the face out of its side 0 -- the cell its normal points away from;cell_offsets/cell_faces: each cell's faces (CSR);cell_face_sides: one u8 per cell -> face entry, 0 if the face is wound out of that cell (it is the face's side 0), 1 if into it. This is the shape path'sside_slotunder the same meaning.
Face numbering is the input's: a producer's face ids (an MPAS edge id) are the face ids, so face data lines up with the file, and nothing re-derives them. Everything else is derived on demand by sorting, and kept:
faces(): the shape path's two-sided record per face,[cell0, local0, cell1, local1](-1, -1for a missing side 1), and the boundary;cell_points(): each cell's points, its face points sorted and unique -- exactly what VTK keeps as a polyhedron's connectivity;edges(): the faces' consecutive point pairs as(low, high)keys, sorted into runs -- the same numbering the shape path gives the same edges.
Ids are the topology's id_dtype (tack.dataset.ids), as on the shape path.
Orientation comes from the input, never from geometry. check_winding lists
the cells that do not walk each of their edges once in each direction;
orient turns a face wound into its only cell around. as_polyhedra
converts any shape-based dataset, reusing its derived faces as they stand.
tack.dataset.PolyhedralTopology ¶
PolyhedralTopology(
face_offsets,
face_points,
cell_offsets,
cell_faces,
cell_face_sides,
num_points=None,
id_dtype=None,
)
Bases: _Topology
Cells given by their faces, faces stored once (docs/vis-guide/design/polyhedra.md).
face_offsets/face_points (CSR) are each face's points, in the order that
winds it out of its side 0; cell_offsets/cell_faces (CSR) each cell's
faces; cell_face_sides (u8, one per cell_faces entry) 0 where the face
is wound out of that cell and 1 where into it. num_points defaults to one
past the highest point id used. Ids are kept in id_dtype, as
UnstructuredTopology keeps them.
Every cell is a Polyhedron: no reference element, so no spaces with a basis
(L2, H1 above order 1) and no parametric coordinates.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
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 | |
faces ¶
faces()
The faces' sides and boundary (PolygonFaces), derived on first use.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
406 407 408 409 410 411 | |
edges ¶
edges()
The faces' edges (PolygonEdges), derived on first use.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
413 414 415 416 417 418 | |
point_links ¶
point_links()
Each point's entries in cell_points(), derived on first use and kept:
(offsets, entries), CSR by point, entries in increasing order (so in
cell order).
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
420 421 422 423 424 425 426 427 428 | |
cell_points ¶
cell_points()
Each cell's points, sorted and unique: (offsets, point_ids), CSR.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
430 431 432 433 434 435 | |
groups ¶
groups()
The cells as one Polyhedron group.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
437 438 439 440 441 442 443 444 445 446 447 | |
from_cell_faces
classmethod
¶
from_cell_faces(
cells,
num_points,
positions=None,
orient=False,
id_dtype=None,
)
A topology from each cell's own list of faces (point-id rings), as VTK and many readers give them: copies of a face are matched by point set, the first copy found is the face -- its cell side 0 -- and a second copy, run the other way, is side 1. Two copies run the same way are refused.
With orient, the copies are made consistent first, on the host:
within each cell, faces walk each shared edge in opposite directions;
across cells, the two copies of a shared face run opposite ways (whole
cells are reversed to make it so); then each connected component is
turned outward as a whole, by the sign of its total volume -- the one
geometric test, made once per component on a sum over many cells, which
thin cells cannot fool as they fool a per-cell test. positions (one
row per point) are needed for that last step.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
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 | |
arrays ¶
arrays()
The five defining arrays as host arrays, in constructor order.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
497 498 499 500 501 | |
tack.dataset.PolygonalTopology ¶
PolygonalTopology(
loop_offsets,
loop_points,
num_points=None,
id_dtype=None,
)
Bases: PolyhedralTopology
Polygons of any number of points, the polyhedral topology one dimension down: cells are polygons, their facets edges.
Built from point loops -- loop_points[loop_offsets[c]:loop_offsets[c + 1]]
is polygon c's points in order -- as producers and isosurfaces give them.
Facet k of a polygon is its edge from loop point k to the next. Edges
are matched by sorting: the first polygon to use an edge is its side 0 and
the edge runs its way; a second must run it the other way, or the winding is
inconsistent (or the surface not orientable) and is refused, as is an edge
of three polygons. loops() gives the loops back.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 | |
loops ¶
loops()
Each polygon's points in order: (loop_offsets, loop_points).
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
696 697 698 | |
tack.dataset.PolylineTopology ¶
PolylineTopology(
line_offsets,
line_points,
num_points=None,
id_dtype=None,
)
Bases: _Topology
Polylines of any number of points, the polyhedral topology two dimensions
down: each cell a chain of points, line_points[line_offsets[c]:line_offsets[c
+ 1]], two or more, as streamlines and VTK's VTK_POLY_LINE give them.
Its facets (faces(), codimension 1) are the chains' ends: a facet per point
where chains end, its normal along the chain out of its side 0 -- the first
chain ending there, in cell order -- and into its side 1, the second. A vertex
has no winding, so any two chains meeting end to end are consistent. Three or
more ending at one point (a branch) the faces cannot hold, and faces()
refuses them, as an edge of three polygons is refused; the topology itself,
and everything that does not need its faces, works. A chain may close (its
last point its first): its two ends are then one interior facet. lines()
gives the chains back; a cell's points (cell_points()) are its chain's,
sorted and unique.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 | |
lines ¶
lines()
Each polyline's points in order: (line_offsets, line_points).
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
800 801 802 | |
cell_points ¶
cell_points()
Each cell's points, sorted and unique: (offsets, point_ids), CSR.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
804 805 806 807 808 809 810 811 812 813 | |
groups ¶
groups()
The cells as one PolyLine group.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
817 818 819 820 821 822 823 824 825 826 | |
faces ¶
faces()
The chains' ends as faces (PolygonFaces of one-point faces), derived on
first use and kept; refuses a point where more than two chains end.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 | |
edges ¶
edges()
Not derived: a polyline's segments are parts of its cells, and nothing needs them as global entities yet.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
860 861 862 863 864 865 | |
tack.dataset.SizeBuckets ¶
Bases: _OnItsArrays
Groups a polyhedral topology's cells by size, for kernels that keep per-cell
scratch: a cell's key is the first cap in caps at least half its facet
points (an upper bound on its edges, so on its crossings of any isovalue).
for_each/launch_groups given the buckets split the cells by them, and
each launch's view carries its cap as the class constant MAX_SCRATCH, so
tack.local_array(dtype, cells.MAX_SCRATCH) has a compile-time size --
as launches split by shape and order. One per topology and caps.
cell_keys ¶
cell_keys()
Each cell's bucket, an i32 array.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 | |
domain_mixin ¶
domain_mixin(key)
The mixin that gives a launch of bucket key its MAX_SCRATCH.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
562 563 564 | |
tack.dataset.orient ¶
orient(topology)
A topology whose faces wound into their only cell are turned around: each such
face's points reversed and its one reference made side 0. Other faces are left as
they are; check_winding judges the rest.
Source code in packages/tack-vis/src/tack/dataset/polyhedra.py
960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 | |
Cell shapes¶
tack.dataset.shapes ¶
Cell shapes: VTK's linear cells as template classes.
Each shape is a @tack.device_class class whose @tack.func methods
are its parametric functions. A kernel takes a shape object as a template
argument and calls its methods directly, so every compiled variant is
specialized to one shape and contains no dispatch on the shape::
@tack.kernel
def centers(cell, conn, xyz, out):
for c in range(out.shape[0]):
pc = cell.parametric_center()
x = tack.vec3(0.0, 0.0, 0.0)
for j in range(cell.NUM_POINTS):
x += cell.shape_function(j, pc) * xyz[conn[c, j]]
out[c] = x
centers(shapes.Hexahedron(), conn, xyz, out)
A mesh of several shapes runs such a kernel once per shape present, over that shape's cells.
Every shape carries VTK's cell type id (Hexahedron.ID is 12, as in
vtkCellType.h), and VTK's point order, parametric coordinates, edges
and faces, so VTK serves as the reference without any remapping:
=========== ==== ====== === ============================================== class ID points dim parametric domain =========== ==== ====== === ============================================== Vertex 1 1 0 the point (0, 0, 0) Line 3 2 1 r in [0, 1] Triangle 5 3 2 r, s >= 0, r + s <= 1 Pixel 8 4 2 r, s in [0, 1]; points in x-fastest order Quad 9 4 2 r, s in [0, 1]; points counterclockwise Tetra 10 4 3 r, s, t >= 0, r + s + t <= 1 Voxel 11 8 3 r, s, t in [0, 1]; points in x-fastest order Hexahedron 12 8 3 r, s, t in [0, 1]; each face counterclockwise Wedge 13 6 3 a triangle in (r, s) times t in [0, 1] Pyramid 14 5 3 r, s, t in [0, 1]; the apex is (0, 0, 1) =========== ==== ====== === ==============================================
Class constants: ID, NUM_POINTS, DIMENSION, NUM_EDGES and
NUM_FACES. Faces are codimension-1: a 3D shape's faces are polygons, a 2D
shape's are its edges (lines, in the order going round the cell), and a line
has none. VTK counts a 2D cell's faces as 0; Tack does not, so a 2D mesh has
faces, as a polygonal one does.
Methods, all callable only from kernels and device functions:
parametric_point(j),parametric_center(): 3-vectors.shape_function(j, pc),shape_gradient(j, pc): VTK'sInterpolateFunctionsandInterpolateDerivsfor pointjat parametric coordinatespc, a 3-vector; the gradient is(d/dr, d/ds, d/dt).is_inside(pc, tol): 1 ifpcis in the parametric domain widened bytol, else 0.edge_corner(e, k),face_corner(f, k),face_shape(f),face_num_points(f): VTK'sGetEdge/GetFaceas indices into the cell's points, and the face's shape id.interpolate(values, pc),interpolate_point(pts, pc),jacobian(pts, pc),world_to_parametric(pts, x),nearest_point(pts, x, pc): the geometry of a cell whose points are given, defined once for every shape.
Coordinates a shape does not use are ignored: a quad's functions do not
read t. A point index must satisfy 0 <= j < NUM_POINTS, and edge
and face indices likewise; others give unspecified values.
The geometric methods take the cell's points from a local array the caller gathered, three coordinates per point, and per-point values from another::
pts = tack.local_array(tack.f32, 3 * cell.NUM_POINTS)
for j in range(cell.NUM_POINTS):
pts[3 * j], pts[3 * j + 1], pts[3 * j + 2] = xyz[conn[c, j]]
pc, found = cell.world_to_parametric(pts, x)
if found and cell.is_inside(pc, 1e-4):
...
Spaces¶
tack.dataset.spaces ¶
Spaces: where a field's values live, on one topology, and the layout that follows.
A space is constructed on a topology (or a dataset, for its topology) and
parameters, as MFEM's FiniteElementSpace is on a mesh::
td.H1(data) # one value per point, interpolated linearly
td.H1(data, order=2) # quadratic: values on points, edges, quad faces, cells
td.L2(data) # each cell's own corner values: linear DG
td.L2(data, order=orders) # an order per cell (0 or 1 here): p-adaptive DG
td.Constant(data) # one value per cell
td.Values(data, "faces") # one value per face, no basis
td.SideTraces(data) # values on each side of each face, at its points
Spaces are interned on their topology: the same kind and parameters on the same topology is the same object, so fields that share a space share its layout -- and equality is identity. The topology holds them weakly: a space holds its topology, and a cycle would keep a dropped dataset's arrays (on a GPU, its device memory) until Python's cycle collector ran. The layout is what a kernel needs beyond a field's values to find them:
H1, order 1: nothing of its own. Its values are the points', found through the topology's connectivity.H1, order 2: its numbering of values -- the points', then one per edge (edge_base + edge), one per quad face (face_dofs) and one per hexahedron (cell_dofs) -- built from the derived edges and faces. Its views read a cell's through the cell's edge and face ids.L2, order 1:offsets, where each cell's values start. An unstructured topology's are its connectivity offsets, so they are shared, not copied. With an order per cell, the offsets come from a scan of each cell's count of values (1 at order 0, its corners at order 1).ConstantandValues: nothing; valuekbelongs to entityk.
Every space knows size, the number of values a field on it holds, and
on, the entities a loop must run over to read it through view.
A space whose cells differ -- an order per cell -- varies: it gives
each cell a key (cell_keys, here the order), and tack.dataset.for_each
launches each shape group once per key, with the view mixin_for(key),
so every launch is specialized as a uniform space's is.
tack.dataset.Space ¶
What every space has: topology, size, on and a field-view mixin,
and the layout attributes that mixin reads.
attributes ¶
attributes(group=None)
The layout a view of this space reads for group, beside the values.
Source code in packages/tack-vis/src/tack/dataset/spaces.py
103 104 105 | |
extra_mixins ¶
extra_mixins(group)
Mixins a field view of this space needs for group beyond its own and
the storage.
Source code in packages/tack-vis/src/tack/dataset/spaces.py
107 108 109 110 | |
mixin_for ¶
mixin_for(key, shape=None)
The field-view mixin for cells of key (None for a uniform space) and
shape.
Source code in packages/tack-vis/src/tack/dataset/spaces.py
112 113 114 115 | |
cell_keys ¶
cell_keys()
For a space that varies: an i32 array of each cell's key, by cell id.
Source code in packages/tack-vis/src/tack/dataset/spaces.py
117 118 119 | |
tack.dataset.H1 ¶
Bases: Space
Continuous: values shared by the cells around them, interpolated by the shape's functions. Order 1 holds one value per point -- today's point data; order 2 adds one per edge, per quad face and per hexahedron or quad, for the shape's quadratic Lagrange functions (tetrahedra, hexahedra, voxels, wedges, triangles, quads and pixels).
tack.dataset.L2 ¶
Bases: Space
Discontinuous: each cell's own values, at offsets[cell]. Order 1 holds a
value at each of the cell's corners -- a linear DG field -- and order 0 one
value per cell. order is one order for every cell, or an array of one per
cell (p-adaptive), read when the space is made.
offsets
property
¶
offsets
Where each cell's values start: value j of cell c is value
offsets[c] + j, and offsets[num_cells] is size.
cell_keys ¶
cell_keys()
Each cell's order, an i32 array (for a space with an order per cell).
Source code in packages/tack-vis/src/tack/dataset/spaces.py
312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 | |
tack.dataset.Values ¶
Bases: Space
One value per entity of kind on -- points, edges, faces or cells -- with no
basis: data about the entity, not a function to interpolate.
On faces, oriented=True says the value is measured along the face's stored
normal (out of its side 0): a normal flux or velocity component, as MPAS's
normalVelocity or upwind_flux's output. Turning a face around -- as
threshold must when it keeps only a face's side-1 cell -- negates it. A
plain face value (an area, an id, a quality) is left alone.
tack.dataset.SideTraces ¶
Bases: Space
Per side: each face holds, for each of its sides, a value at each of its
points (in the face's own row) -- a field's trace from each cell onto the
face, MFEM's double-valued face E-vector. Laid out (face * 2 + side) * 4 +
point, triangles leaving the fourth slot unused and boundary faces side 1.
tack.dataset.algorithms.traces fills one from any field with a basis.
Sets, ghost marks and global ids¶
tack.dataset.Set ¶
Set(data, on, ids)
Some of a topology's on entities -- "points", "cells",
"faces" or "edges" -- by id: Set(data, on, ids), ids a field, an array or a
list. Duplicates collapse and the ids are kept sorted (ids, a field of
the topology's id_dtype); an id outside the topology is refused.
A face set is also a side set: on the boundary a face has one side, so its
face names its cell's side; Set.from_sides takes (cell, local face)
pairs, as Exodus side sets give them. Put a set in data.sets to name it.
Source code in packages/tack-vis/src/tack/dataset/sets.py
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | |
from_flags
classmethod
¶
from_flags(data, on, flags)
The on entities whose flag is not zero: flags holds one value per
entity -- a Tack array of any integer or floating type, a field (its
values), a NumPy array or a list -- as a kernel that tests each entity
writes them. extract_cells of a cell set keeps those cells, so a
filter that selects cells needs no compaction of its own.
Source code in packages/tack-vis/src/tack/dataset/sets.py
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 | |
from_sides
classmethod
¶
from_sides(data, cells, local_faces)
The faces named by (cell, local face) pairs -- local faces numbered as the cell's view numbers them (VTK's order on the shape path, the cell's own face list on the polyhedral one). Which side was named is not kept: on the boundary, a face has only the one.
Source code in packages/tack-vis/src/tack/dataset/sets.py
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 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | |
flags ¶
flags()
Membership: an i32 array, 1 for each entity in the set, else 0.
Source code in packages/tack-vis/src/tack/dataset/sets.py
220 221 222 223 224 225 226 227 228 229 | |
cell_keys ¶
cell_keys()
Each cell's key, for splitting the cells' groups: its membership.
Source code in packages/tack-vis/src/tack/dataset/sets.py
231 232 233 | |
numpy ¶
numpy()
The ids, sorted, on the host.
Source code in packages/tack-vis/src/tack/dataset/sets.py
238 239 240 | |
__array__ ¶
__array__(dtype=None, copy=None)
np.asarray(set): the ids, sorted, on the host.
Source code in packages/tack-vis/src/tack/dataset/sets.py
242 243 244 245 246 247 | |
tack.dataset.Ghosts ¶
Ghosts(cells=None, points=None)
Ghost marks of a dataset's cells and points: one u8 per cell (cells) and
per point (points), either of which may be None, holding VTK's
vtkGhostType bits.
A cell or point marked DUPLICATECELL or DUPLICATEPOINT is a copy of one
another piece owns. HIDDENCELL and HIDDENPOINT mark data that is not
valid there (a blanked cell). REFINEDCELL marks a cell a finer AMR
level covers, whose data are valid but superseded, and REFINEDPOINT (a
Tack bit; VTK has no such point bit) a point at whose location a finer
level has one. Filters that make cells leave hidden and refined cells out
(MASKED_CELLS); reductions leave out every skipped bit. The other bits
are carried, not read.
Source code in packages/tack-vis/src/tack/dataset/ghosts.py
122 123 124 125 | |
to_device ¶
to_device(device)
These marks on device.
Source code in packages/tack-vis/src/tack/dataset/ghosts.py
136 137 138 139 140 | |
tack.dataset.GlobalIds ¶
GlobalIds(cells=None, points=None)
Global ids of a dataset's points (points) and cells (cells): one
i64 each, either of which may be None. A point or cell several pieces
hold has the same id in each; ids are unique across the pieces otherwise.
Source code in packages/tack-vis/src/tack/dataset/global_ids.py
29 30 31 32 | |
to_device ¶
to_device(device)
These ids on device.
Source code in packages/tack-vis/src/tack/dataset/global_ids.py
44 45 46 47 48 | |
Implicit functions¶
tack.dataset.implicit ¶
Implicit functions: regions of space by the sign of a function.
value(p) is negative inside, zero on the surface and positive outside,
in the forms VTK (vtkImplicitFunction) and Viskores
(viskores::ImplicitFunction) use, so a slice by one lands on the same
points as theirs: a sphere and a cylinder are quadrics (squared distance
minus squared radius), a plane and a set of planes signed distances (a set's
the largest of its planes'), a box the signed distance to it.
Each is a @tack.device_class object whose parameters are instance
values: a kernel takes it as a template and calls f.value(p) on a
3-vector, and moving a sphere or resizing a box does not compile anything
anew. algorithms.implicit_values evaluates one on a dataset's geometry;
slice, clip, extract_geometry and extract_points take one.
Union, Intersection and Difference combine them, as VTK's
vtkImplicitBoolean does (the smaller value, the larger, the first's or the
second's negated), on the device: a kernel inlines the whole tree.
Parameters are positions in world coordinates. On a dataset with an origin
(DataSet.origin) a filter evaluates the function at the geometry's values
-- positions relative to that origin -- through relative_to: each
function here moves its parameters by the origin in double precision
(_relative_to), so nothing is lost far from zero; any other object with a
value gets the origin added to the position it is given, in the
geometry's type.
Every product in a value is named, so the float contract rounds it on its
own and no backend fuses it into a sum: a function gives the same bits on
every backend. A slice through mesh points -- whose values are then exactly
the level, and merge (contour) -- cuts alike everywhere; with a fused
multiply-add, Metal's noise around zero cut a plane through grid points into
1015 triangles where the CPU made 883.
tack.dataset.Plane ¶
Plane(origin, normal)
The half-space below a plane: n . (p - origin), n the unit normal.
Source code in packages/tack-vis/src/tack/dataset/implicit.py
82 83 84 | |
tack.dataset.Sphere ¶
Sphere(center, radius)
A ball: |p - center|^2 - radius^2.
Source code in packages/tack-vis/src/tack/dataset/implicit.py
103 104 105 106 107 | |
tack.dataset.Cylinder ¶
Cylinder(center, axis, radius)
An infinite cylinder about the line through center along axis: the
squared distance from that line minus radius^2.
Source code in packages/tack-vis/src/tack/dataset/implicit.py
129 130 131 132 133 134 | |
tack.dataset.Box ¶
Box(lower, upper)
An axis-aligned box from lower to upper: the signed distance to it
(negative inside, by the distance to the nearest face).
Source code in packages/tack-vis/src/tack/dataset/implicit.py
166 167 168 169 170 171 | |
tack.dataset.Planes ¶
Planes(origins, normals)
The convex region below every one of several planes (a frustum, a clipping box at any angle): the largest of their signed distances.
The planes follow the data's precision, as the other functions' Python
numbers do: implicit_values, and every filter that takes an implicit
function, evaluate a copy holding them in the geometry's floating type (f64
for f64 data) on its device (_matching). Made, it holds them in f32 on
the default device, which a kernel of one's own calling value reads.
Source code in packages/tack-vis/src/tack/dataset/implicit.py
202 203 204 205 206 207 208 209 210 211 212 213 214 | |
tack.dataset.Union ¶
Union(first, second, *more)
Bases: _Boolean
Inside either function's region: the smaller of their values.
Source code in packages/tack-vis/src/tack/dataset/implicit.py
260 261 262 | |
tack.dataset.Intersection ¶
Intersection(first, second, *more)
Bases: _Boolean
Inside both functions' regions: the larger of their values.
Source code in packages/tack-vis/src/tack/dataset/implicit.py
260 261 262 | |
tack.dataset.Difference ¶
Difference(first, second, *more)
Bases: _Boolean
Inside the first function's region and outside the others': the first's value or the second's negated, whichever is larger.
Source code in packages/tack-vis/src/tack/dataset/implicit.py
300 301 302 303 | |
Locating points¶
tack.dataset.CellLocator ¶
CellLocator(data, density=1.0, levels=2)
Locates points in data's cells; build once, find many times. density
is the bins per cell of the uniform grid a search from the cells buckets
the points in; the search from the points reads the cell bins
(cell_bins): with levels=2 (the default) a two-level grid, as
Viskores' CellLocatorTwoLevel, which holds meshes graded toward a body
in a large far field; with levels=1 that uniform grid, as Viskores'
CellLocatorUniformBins and VTK's vtkStaticCellLocator, fewer
candidates a search on a small, even mesh, and a few bins holding most of
the cells on a graded one.
The bins follow the cells' bounding boxes when it is made. It records the
geometry it was made from and that geometry's state (serves): probe
and advection take it for any dataset of the same topology and geometry,
and refuse it once Tack has written the positions since (a kernel, fill
or copyto into them). Without one they use the locator the topology
keeps (cached_locator), made again when the geometry changes. Writes
Tack does not see -- through memory shared by DLPack, export_memory or
wrap_ptr -- need a new locator.
Source code in packages/tack-vis/src/tack/dataset/locator.py
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 | |
serves ¶
serves(data)
Whether it locates in data's cells as they are: the topology and the
geometry's values it was made from, with nothing written to them by Tack
since.
Source code in packages/tack-vis/src/tack/dataset/locator.py
519 520 521 522 523 524 525 | |
cell_bins ¶
cell_bins()
The cells each leaf may hold, for queries that run from the points (a
particle asking for its cell): (offsets, cells), CSR by leaf, the
cells whose boxes, widened by their tolerance, overlap the leaf. With
two levels the leaves are a two-level grid: a coarse uniform grid of
about _CELLS_PER_BIN cells a bin, each bin a uniform grid of leaves
sized by its count (leaves); with one, the uniform grid of about
density bins a cell, each bin one leaf. Made on first use and kept.
Source code in packages/tack-vis/src/tack/dataset/locator.py
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 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 | |
leaves ¶
leaves()
The two-level grid's leaves (cell_bins): (dims, starts), each
coarse bin's leaves along x, y and z (dims[3 * b + axis]) and its
first leaf's index.
Source code in packages/tack-vis/src/tack/dataset/locator.py
583 584 585 586 587 588 | |
find ¶
find(points)
Locations(cells, pcs, tets) for points (an (n, 3) host array or
a field of 3-vectors): each point's cell (-1 for none) and its
coordinates there, and in polyhedra its sub-tetrahedron (tets is
None otherwise). See Locations.
Source code in packages/tack-vis/src/tack/dataset/locator.py
605 606 607 608 609 610 611 | |
tack.dataset.Locations ¶
Bases: NamedTuple
Where CellLocator.find put each point: cells, the cell holding it (-1
for none), in the dataset's id_dtype; pcs, its coordinates there; and
tets, in polyhedra, its sub-tetrahedron in the cell (i32, -1 for none),
None on cells with a reference element. On those pcs are parametric
coordinates; in a polyhedron they are the weights of the sub-tetrahedron's
face point and edge points (views._PolyhedralLocation), which a field's
view evaluates with tet_value(c, t, pc).
tack.dataset.PointLocator ¶
PointLocator(
data, bin_size=None, points_per_bin=4, periodic=None
)
Uniform bins over data's points, for finding points near points; build
once, query many times. bin_size is a bin's side (in the geometry's
units); by default bins hold about points_per_bin points each, cubes
along the axes the points span.
periodic is a periodic box, in world coordinates: its side (a number,
the box from zero), its three sides, or (lower, upper) corners. The bins
then tile the box exactly, points outside it are wrapped in, and every
distance is to a point's nearest image.
It records the geometry it was made from and that geometry's state
(serves), as CellLocator does: filters take it for any dataset of
the same topology and geometry, and refuse it once Tack has written the
positions since.
Source code in packages/tack-vis/src/tack/dataset/point_locator.py
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 | |
serves ¶
serves(data, periodic=None)
Whether it finds data's points as they are -- the topology and the
geometry's values it was made from, with nothing written to them by Tack
since -- in the periodic box given (or none).
Source code in packages/tack-vis/src/tack/dataset/point_locator.py
358 359 360 361 362 363 364 365 | |
grid ¶
grid()
The sorted points and the grid, as a template for kernels (_PointGrid).
Source code in packages/tack-vis/src/tack/dataset/point_locator.py
367 368 369 370 | |
check_radius ¶
check_radius(radius)
Refuse a search radius of half a periodic box or more: a point's nearest image is then not the only one within it.
Source code in packages/tack-vis/src/tack/dataset/point_locator.py
372 373 374 375 376 377 | |
neighbors ¶
neighbors(radius, points=None)
Every point within radius (inclusive) of each query point, CSR:
(offsets, ids), the neighbours of query q at ids[offsets[q]:
offsets[q + 1]], by bin and then by index. The queries are the points
themselves (each leaving itself out) unless points are given.
Source code in packages/tack-vis/src/tack/dataset/point_locator.py
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 | |
nearest ¶
nearest(k, points=None)
The k nearest points (1 <= k <= MAX_K) to each query point:
(ids, squared_distances), each (m, k), nearest first, ties by
index; -1 (and a squared distance of -1) where there are fewer points
than k. The queries are the points themselves (each leaving itself
out) unless points are given.
Source code in packages/tack-vis/src/tack/dataset/point_locator.py
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 | |
Steps in time¶
tack.dataset.RunningStatistics ¶
RunningStatistics(fields)
Bases: _Streamed
Per point or cell, statistics of fields (a name or names) over the steps
so far: each update(data) adds the step's values and returns the step with
<name>_mean, <name>_variance (the population variance, M2 / n),
<name>_min and <name>_max beside its fields, computed by Welford's
method on the device (in f64 where the device has it). Vector fields are taken
component by component.
The steps must be on one mesh -- the same topology, or one of the same kind,
sizes and arrays -- or the update is refused: remesh onto the first step's mesh
first. Nothing of earlier steps is kept but the sums, so nothing is copied.
The mean, minimum and maximum are in the field's unit, the variance in its
square (kelvin squared for degrees Celsius). count is the steps added.
Source code in packages/tack-vis/src/tack/dataset/temporal.py
155 156 157 158 159 | |
tack.dataset.TimeDerivative ¶
TimeDerivative(fields, copy=True)
Bases: _Streamed
Each of fields' rate of change since the previous step: update(data)
returns the step with <name>_dt, (value - previous) / (time - previous
time) per point or cell (a backward difference, in f64 where the device has
it), in the field's unit over the time unit (a field in degrees Celsius gives
kelvin per second). The first step has no previous one: its update returns
None.
The previous step's values are kept as copies (copy=False keeps
references, where the caller's buffers are not overwritten between steps).
The steps must be on one mesh and in increasing time, with times in units
that convert (_elapsed).
Source code in packages/tack-vis/src/tack/dataset/temporal.py
321 322 323 | |
tack.dataset.Advection ¶
Advection(
field,
seeds=None,
integrator="rk4",
step_size=None,
copy=True,
)
Particles carried by field (a velocity field's name) through the steps:
each update(data) moves every particle from its time to the step's, with
the velocity linear in time between the previous step (kept, as a copy unless
copy is false) and this one, and returns the particles at their current
points -- a point cloud at the step's time with seed (each particle's
number), source (the seed point it came from, or -1), release (its
release time), time and status (ParticleStatus bits, as
advect's).
seeds (an (n, 3) array of world positions) are released at the first
step's time; inject(points, time=None) adds particles at any later time
(by default the last step's). The velocity is the probe of each step's own
mesh, so the mesh may change between steps, and over a PartitionedDataSet
each point is probed on the rank whose cells hold it: the particles stay on
the rank that released them. integrator is "rk4" or "euler";
step_size (in the steps' time unit) bounds each substep, by default one
substep from step to step. A particle leaving the mesh stops where it last
was, marked SPATIAL_BOUNDS; one released outside has SUCCESS
cleared. Times convert between the steps' unit and the velocity's (a
velocity in m/s over steps in minutes).
Source code in packages/tack-vis/src/tack/dataset/temporal.py
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 | |
inject ¶
inject(points, time=None, sources=None)
Release particles at world points at time (in the steps' time
unit; by default the last step's, or the first step's before any); the next
update carries each from its release time. sources numbers the seed
point each came from (-1 by default). A time before the last step is
refused: that interval has been integrated.
Source code in packages/tack-vis/src/tack/dataset/temporal.py
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 | |
update ¶
update(data)
Carry the particles to data's time and return them (see the class).
Source code in packages/tack-vis/src/tack/dataset/temporal.py
498 499 500 501 502 503 504 505 506 507 508 | |
tack.dataset.Pathlines ¶
Pathlines(
field,
seeds=None,
integrator="rk4",
step_size=None,
copy=True,
)
Each particle's path through the steps: Advection (its arguments) and
extend_paths, each update(data) returning the current pathlines -- a
polyline per particle (seed and time on the points), a particle's
first point alone until it has moved.
inject releases particles as Advection.inject does.
Source code in packages/tack-vis/src/tack/dataset/temporal.py
631 632 633 | |
tack.dataset.Streaklines ¶
Streaklines(
field,
seeds,
releases=1,
integrator="rk4",
step_size=None,
copy=True,
)
The lines of particles released at seeds (world positions) through the
steps: every update releases releases particles at each seed, at times
spread evenly over the interval since the previous step (one at each step's
time by default), carries them all (Advection), and returns a polyline per
seed (source on the cells) through the particles it released still in the
mesh, newest (at the seed) first, with source, release and time on
the points; a seed with one particle so far keeps its point, in no line.
Source code in packages/tack-vis/src/tack/dataset/temporal.py
652 653 654 655 656 657 658 | |
tack.dataset.extend_paths ¶
extend_paths(paths, particles)
paths (None, or what this returned before) with particles -- an
Advection's output -- appended: each particle's points in time (seed
and time on the points), and a polyline through those of each particle
with two or more. Each step adds the particles that moved since (a
stopped one adds nothing). It stands alone, so particles written step by
step can be joined into paths afterwards.
Source code in packages/tack-vis/src/tack/dataset/temporal.py
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 | |
Kernels over meshes¶
tack.dataset.for_each ¶
for_each(kernel, data, domain, *args)
Run kernel(view, *args) once per shape in domain of data.
view is the domain's view for that shape (for i in view); every
Field among args becomes its view for the same shape, so
u.value(i, pc) and u.at(i) read the entity i the loop is at.
Other arguments pass through.
domain may also be a Space: the kernel then runs over the space's
nodes (DataSet.node_views), taking arrays, not fields.
Source code in packages/tack-vis/src/tack/dataset/dataset.py
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 | |
Filters of your own¶
What the built-in filters are written with. A filter that makes a new
dataset states how its output's points, cells, faces and edges come from
the input's, and carry takes the fields, sets, ghost marks, global ids,
origin, unit and time along by those maps. A filter that builds its
DataSet some other way passes its input's marks on with keep_marks (the
same cells and points) or records their loss with drop_marks, so that
reductions over pieces refuse what they would count twice. Filters that
make cells start from visible(data), which leaves out hidden cells, and a
reduction over a piece takes its owned_values. A selection made by a
kernel becomes a set with Set.from_flags, and extract_cells keeps it.
tack.dataset.carry ¶
How a filter's output takes its input's fields.
A filter that makes a new dataset says how each kind of output entity comes
from the input -- the same entity (Same), entity ids[i] of the input
(Take), a piece of input cell ids[i] (Pieces: a contour's
triangles, a surface's faces), or a point between two input points
(Interpolate) -- and carry applies one set of rules to every field:
- point data (
H1, values on points) goes through the point map. An order-2 field brings its corner values, as the filters read it. A field is interpolated only if its values are floating point; an integer field meets anInterpolatemap and is left behind. - cell data (
Constant, values on cells) goes through the cell map and keeps its kind. - L2 (DG) data is copied cell by cell, keeping each cell's order, when the cell map keeps or takes whole cells (not pieces of them) and the output has reference cells to lay it out on.
- values on faces go through the face map -- negated where the map
turns a face around, if they are oriented -- or become values on the
output's cells when those cells are input faces (
faces_to_cells). - values on edges go through the edge map, which only a filter whose
edges are the input's gives (
as_polyhedra), or become values on the output's cells when those cells are input edges (edges_to_cells). - traces, and fields on any entity without a map, are left behind: the output's own edges and faces are derived afresh, so the old numbering means nothing there.
The geometry follows the same rules unless the filter computed positions
itself. fields selects which fields come along: None (all), a list
of names, or an empty list (none); naming a field the input does not have
raises KeyError.
carry ¶
carry(
data,
out,
*,
points=None,
cells=None,
faces=None,
faces_to_cells=None,
edges=None,
edges_to_cells=None,
geometry=None,
fields=None,
sets=None,
)
A dataset on topology out with data's fields, by the maps given.
points, cells, faces and edges say how out's come from
data's (Same, Take, Pieces for cells, Interpolate for
points); faces_to_cells (edges_to_cells) that out's cells are
data's faces (edges). A
kind of entity with no map takes no fields. geometry is the output's
geometry field when the filter computed it, otherwise the input's is
carried like any point (or L2) field.
Ghost marks go by the same maps (ghosts.carry_marks): what the output makes
takes the owner of what it came from. Global ids go with the entities that are
input entities (global_ids.carry_ids).
Sets go by the same maps: an output entity is in a set when the input entity
it comes from is, and a face or edge set becomes a cell set where faces or
edges become cells (faces_to_cells, edges_to_cells). A set whose kind has no map, or one that makes new
entities (Interpolate), is left behind; sets, if given, replaces
them.
Source code in packages/tack-vis/src/tack/dataset/carry.py
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 | |
tack.dataset.Same ¶
Output entity i is input entity i.
tack.dataset.Take ¶
Take(ids, turned=None)
Output entity i is input entity ids[i]. turned, for faces: an
i32 array, 1 where the output face is the input face turned around, so
its oriented values change sign.
Source code in packages/tack-vis/src/tack/dataset/carry.py
65 66 67 68 | |
tack.dataset.Pieces ¶
Pieces(ids)
Bases: Take
Output cell i lies inside input cell ids[i]: it takes that cell's
values, but not its L2 data, which belongs to the whole cell's shape.
Source code in packages/tack-vis/src/tack/dataset/carry.py
75 76 | |
tack.dataset.Interpolate ¶
Interpolate(
ends, weights, averages=None, average_cells=None
)
Output point i lies between input points ends[i] (a 2-vector of ids)
at weights[i] from the first. averages, (offsets, members): further
output points after those, each the average of earlier output points
members[offsets[k]:offsets[k + 1]] (clip's centroid points), and inside
input cell average_cells[k], whose ghost mark it takes.
Source code in packages/tack-vis/src/tack/dataset/carry.py
86 87 88 89 90 | |
tack.dataset.keep_marks ¶
keep_marks(source, out)
out, a dataset on source's cells and points (the same topology), with
source's ghost marks -- and its record of having lost them -- and its
origin, spatial unit and time.
Source code in packages/tack-vis/src/tack/dataset/ghosts.py
40 41 42 43 44 45 46 47 48 | |
tack.dataset.drop_marks ¶
drop_marks(source, out, reason=True)
out, made from source's cells and points by a filter that cannot give
it ghost marks: if source had marks, or had lost them, record that
out lost them (reason, a sentence saying why, or True), so
reductions refuse it rather than count shared cells and points once per
piece.
Source code in packages/tack-vis/src/tack/dataset/ghosts.py
51 52 53 54 55 56 57 58 59 60 | |
tack.dataset.visible ¶
visible(data, *arguments)
data without its hidden cells (HIDDENCELL: data not valid there) and
refined cells (REFINEDCELL: an AMR cell a finer level covers), which
filters that make cells never process: data itself when it has none,
else its other cells as extract_cells keeps them, marks and fields
carried. Given field arguments too (Fields of
data or names; anything else passes through), returns (data,
*arguments), each Field carried onto the cells kept.
Source code in packages/tack-vis/src/tack/dataset/ghosts.py
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 | |
tack.dataset.owned_values ¶
owned_values(data, field)
The values of field (a Field of data, or its name) that data owns: all of
them when it has no marks for where they live, else those of cells and
points not marked a duplicate or hidden, materialized. Fields on faces or
edges, and order-2 fields, have no marks of their own yet; on a dataset
with ghost marks they are refused rather than counted twice.
Source code in packages/tack-vis/src/tack/dataset/ghosts.py
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 215 216 217 218 219 220 221 222 223 224 225 226 227 228 | |
Units¶
A filter's parameters take quantities ("300 K", (1, "cm")) where the
built-ins do: units.magnitude(value, field.unit, "the bounds") gives a
number in a field's unit, units.magnitude(value, data.unit, "the radius")
one in the spatial unit, and a plain number passes through. A field a filter
computes gets the unit of what it is: units.delta for a difference (a
difference of degrees Celsius is in kelvin), units.derived for a product
of powers, units.named for the SI name. Unknown in, unknown out: None,
never a guess.
tack.dataset.units ¶
Physical units of fields: plain strings, checked and combined by unyt.
A field's unit (Field.unit) is meaning, not storage: arrays and kernels
never see one. None is unknown and "dimensionless" ("1" on the way
in) is a pure number; nothing derived from an unknown unit is guessed. A
dataset's spatial unit (DataSet.unit) is its geometry field's, and its
origin is in it.
Units are stored as unyt's canonical spelling, so "m/s", "m*s**-1" and
"m/s^1" are one unit: ^ is read as a power, as LLNL units and VTK
write it. Code units are scaled units ("3.0857e21*cm"), which need no
registry of their own. Tack's registry is unyt's plus the US survey foot,
ftUS (1200/3937 m), which State Plane coordinates and older LiDAR use and
which differs from the international foot by 2 parts per million.
Derived units (derived) are products of powers of units; None in gives
None out. Units with an offset zero (degrees Celsius and Fahrenheit) are
not multiplied: a difference of them, as a gradient, a jump or a variance
takes, is in kelvin (Rankine), the same size of degree with zero at zero
(delta). named shows a derived unit by its SI name (Pa*m**3 as
J) where a filter names what it computes.
Parameters take quantities (quantity, magnitude): a string such as
"10 cm", a pair (value, unit) (the value a number or an array), or a
unyt or pint quantity. A plain number is in the unit the parameter is in (the
geometry's for a length, the field's for an isovalue), as before.
unyt (with sympy) is imported on first use, so a program that gives no unit pays nothing. Tack hands out strings only, never unyt objects.
canonical ¶
canonical(unit)
unit (a string, or None for unknown) in unyt's canonical spelling;
a string that is no unit raises ValueError.
Source code in packages/tack-vis/src/tack/dataset/units.py
65 66 67 68 69 70 71 72 73 74 75 | |
conversion ¶
conversion(source, target)
(factor, offset): a value in source is factor * value + offset
in target. Both must be known and of the same dimensions.
Source code in packages/tack-vis/src/tack/dataset/units.py
78 79 80 81 82 83 84 85 86 87 88 89 90 91 | |
same ¶
same(a, b)
Whether units a and b (canonical strings or None) are one unit:
equal, or both known and one a relabelling of the other (factor 1, no
offset). Unknown is the same only as unknown.
Source code in packages/tack-vis/src/tack/dataset/units.py
94 95 96 97 98 99 100 101 102 103 104 105 106 | |
compatible ¶
compatible(a, b)
Whether units a and b may meet: one unit, or either unknown (which
cannot be checked, so it is not refused).
Source code in packages/tack-vis/src/tack/dataset/units.py
109 110 111 112 | |
common ¶
common(what, units)
The one unit of units (canonical strings or None), or ValueError
naming what ("the pieces' geometry", say) when two known units
differ. None if any is unknown.
Source code in packages/tack-vis/src/tack/dataset/units.py
115 116 117 118 119 120 121 122 123 124 125 | |
check ¶
check(what, a, b)
ValueError naming what unless units a and b may meet
(compatible).
Source code in packages/tack-vis/src/tack/dataset/units.py
128 129 130 131 132 133 | |
delta ¶
delta(unit)
The unit of a difference of values in unit: itself, or for a unit with an
offset zero, the absolute unit of the same size (kelvin for degrees Celsius,
Rankine for Fahrenheit). None stays None.
Source code in packages/tack-vis/src/tack/dataset/units.py
136 137 138 139 140 141 142 143 144 145 146 147 148 | |
derived ¶
derived(*terms)
The unit of a product of powers: terms are (unit, exponent) pairs (a
bare unit is to the first power). None if any unit is unknown; a unit
with an offset zero is refused (take its delta first, or convert).
Source code in packages/tack-vis/src/tack/dataset/units.py
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 | |
named ¶
named(unit)
unit shown by the SI derived unit of its dimensions and size, if one is
("Pa*m**3" as "J", "kg*m/s**2" as "N"), else unchanged. Only
the dimensions and the size decide, never how the unit was written.
Source code in packages/tack-vis/src/tack/dataset/units.py
178 179 180 181 182 183 184 185 186 187 188 189 190 | |
dimensionless_or_none ¶
dimensionless_or_none(what, unit)
"dimensionless" for a pure number, None for unknown, and
ValueError naming what for anything with dimensions or a scale (a
logarithm of metres, or of kilometres per metre, depends on the unit).
Source code in packages/tack-vis/src/tack/dataset/units.py
193 194 195 196 197 198 199 200 201 202 203 | |
quantity ¶
quantity(value)
(magnitude, unit) of a quantity -- a string "10 cm", a pair (value,
unit) with unit a string, or a unyt or pint quantity (.units and
.value or .magnitude) -- or None for anything else (a plain
number, an array, a field's name is not parsed here). A string with a number
and no unit is a pure number.
Source code in packages/tack-vis/src/tack/dataset/units.py
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 | |
magnitude ¶
magnitude(value, unit, what)
value as a number (or numbers) in unit: a quantity (quantity)
converted, scale and offset; a sequence of quantities each; anything else
as given, taken to be in unit already. Converting a quantity needs
unit known; what names the parameter in the refusal.
Source code in packages/tack-vis/src/tack/dataset/units.py
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | |
number_and_unit ¶
number_and_unit(value)
(number, unit) of a parameter that may bring its own unit (a mass, a
velocity): a quantity's magnitude and unit, else the value as given and
None.
Source code in packages/tack-vis/src/tack/dataset/units.py
260 261 262 263 264 265 | |
time_unit ¶
time_unit(unit)
unit (a string or None) canonical, refused unless it is a unit of
time.
Source code in packages/tack-vis/src/tack/dataset/units.py
268 269 270 271 272 273 274 275 276 | |
Interoperability¶
tack.interop.vtk ¶
tack.interop.vtk — Zero-copy interop between VTK arrays and Tack arrays.
from tack.interop.vtk import vtk_to_array, array_to_vtk
field = vtk_to_array(vtk_array) # VTK -> Tack
array = array_to_vtk(field) # Tack -> VTK
Both directions share memory rather than copying, on the host and on the device, and each side keeps the other alive for as long as it is needed.
Shape¶
A vtkDataArray is tuples x components, and a Tack array is n-dimensional,
so the two line up directly: a 2-D array of shape (n, 3) is n tuples of
3 components, and field.shape[1] is the component count. Nothing needs
to be declared -- the shape already says it.
(1000, 3) field <-> 1000 tuples x 3 components
(1000,) field <-> 1000 tuples x 1 component
The one wrinkle is that Tack's own visualization algorithms work on flat
interleaved arrays -- flying_edges returns points as (n*3,), and
compute_normals indexes points[i * 3 + c]. For those, ask for the
flat form explicitly:
points = vtk_to_array(vtk_points, flatten=True) # (n*3,)
array = array_to_vtk(points, n_components=3) # back to n x 3
Requirements¶
VTK with vtkmodules.util.dlpack_support. Device arrays additionally
need VTK built with Viskores; host arrays work with any VTK that has the
DLPack module.
Level Zero¶
CUDA and HIP pointers identify themselves: the runtime keeps one context per device for the whole process. A Level Zero pointer does not -- it means something only inside the context it was allocated from, and DLPack has no field for a context. So the two libraries have to be in the same one before any array is exchanged:
from tack.interop.vtk import init_level_zero
init_level_zero() # instead of tack.set_default_device("level_zero")
That starts Tack's Level Zero backend inside the context VTK's Viskores device already uses. It needs VTK built with Viskores on Kokkos' SYCL backend. Fields made before the call belong to another context and cannot be shared.
init_level_zero ¶
init_level_zero()
Make a Level Zero device in the context VTK's device memory uses the default device, and return it.
Raises RuntimeError if this VTK cannot report one: it predates
dlpack_support.level_zero_handles, was built without Viskores on
Kokkos' SYCL backend, or is not running on a Level Zero device.
Source code in packages/tack-vis/src/tack/interop/vtk.py
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | |
vtk_to_array ¶
vtk_to_array(vtk_array, flatten=False)
Wrap a vtkDataArray as a Tack array, without copying.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vtk_array
|
any vtkDataArray, on the host or on a device. |
required | |
flatten
|
return a 1-D array of |
False
|
Returns:
| Type | Description |
|---|---|
|
A Tack array sharing the VTK array's memory, shaped
|
The array is on the default device when that can address the memory, else on the memory's own device: a CUDA array with the CPU as default is a CUDA array.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
for Level Zero memory from another context than
Tack's (see |
ValueError
|
for an array with no memory to share (implicit and computed arrays have none) or a non-contiguous layout. |
Source code in packages/tack-vis/src/tack/interop/vtk.py
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 | |
array_to_vtk ¶
array_to_vtk(array, n_components=None, name=None)
Wrap a Tack array as a vtkDataArray, without copying.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
array
|
the Tack array to share. A 2-D array maps straight over --
|
required | |
n_components
|
only needed for a flat array holding interleaved
data, which is the form Tack's own algorithms produce. A
|
None
|
|
name
|
array name, as VTK uses to identify it in a dataset. |
None
|
Returns:
| Type | Description |
|---|---|
|
A vtkDataArray sharing the array's memory. The array is held for as long as the array lives. |
Raises:
| Type | Description |
|---|---|
ValueError
|
if n_components contradicts the array's shape, does not divide its size, or the array has more than 2 dimensions. |
Source code in packages/tack-vis/src/tack/interop/vtk.py
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 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 | |
vtk_to_dataset ¶
vtk_to_dataset(
grid,
dtype=tack.f32,
polyhedral=None,
orient=False,
id_dtype=None,
origin=None,
)
Copy a vtkUnstructuredGrid, vtkPolyData, vtkStructuredGrid,
vtkRectilinearGrid or vtkImageData into a tack.dataset.DataSet: an image's points as
UniformCoordinates (with its direction matrix), a rectilinear grid's as a
CartesianProduct, a structured grid's as its explicit points.
Points and floating-point arrays become fields of dtype; integer
arrays keep their type. Named point data become H1 fields and named
cell data Constant fields (a cell array named as a point array is
"<name> (cells)", with a warning); a vtkGhostType array becomes the ghost marks
(data.ghosts), not a field; a constant or affine array (VTK's implicit
arrays) stays implicit, as a ConstantArray or CountingArray. An
unstructured grid's cells must all be linear shapes -- unless it holds
polyhedra (or polyhedral is true), when it becomes a
PolyhedralTopology, every cell a polyhedron of its faces, polylines,
when it becomes a PolylineTopology (its lines two-point polylines, and
no other cells allowed), or polygons (VTK_POLYGON), when it becomes a
PolygonalTopology (its triangles and quads polygons too, and no other
cells allowed). Its types, offsets and connectivity stay implicit where
VTK's are (a cell type given as a number, affine offsets).
A vtkPolyData is taken as the unstructured grid of its cells in VTK's
order -- verts, lines, polygons (of three or four points triangles and
quads), and triangle strips, each strip as its triangles, which take its
cell data -- so the same rules hold: a surface of triangles and quads is
an UnstructuredTopology, one with larger polygons a PolygonalTopology,
lines a PolylineTopology, and polylines or polygons beside other cells
are refused.
With orient, inconsistently wound polyhedra are repaired first
(PolyhedralTopology.from_cell_faces); without it they are refused.
VTK's ids are 64-bit; the topology keeps them in id_dtype, by default
i32 unless the grid is too large for it (tack.dataset.ids).
origin (three numbers) becomes the dataset's origin: VTK's points,
an image's origin and a rectilinear grid's lines are taken relative to it,
subtracted in double precision before they become dtype, so points far
from zero keep their precision.
A vtkOverlappingAMR becomes an AMRDataSet: each block (an image or a
rectilinear grid) converted as above, placed in its level by its
vtkAMRBox, the levels' refinement ratios kept; the refined cells are
marked afresh (VTK's vtkGhostType bits come in and are recomputed).
Blocks with ghost layers are refused.
A vtkPartitionedDataSet becomes a PartitionedDataSet, each piece
converted as above (null pieces skipped), and a
vtkPartitionedDataSetCollection a dict of them, keyed by each
partitioned dataset's vtkCompositeDataSet.NAME() ("block<i>" where
it has none, "<name>#<i>" for a name already taken).
VTK's DATA_TIME_STEP becomes the step's time (time), its unit
unknown.
Source code in packages/tack-vis/src/tack/interop/vtk.py
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 | |
dataset_to_vtk ¶
dataset_to_vtk(data)
Copy a tack.dataset.DataSet into a new VTK dataset; an AMRDataSet of
images and rectilinear grids, its ratios the same along every axis, into a
vtkOverlappingAMR (its marks as vtkGhostType arrays), any other into
a vtkPartitionedDataSetCollection of a partitioned dataset per level
("level<l>"), VTK's AMR holding no curvilinear grids; a
PartitionedDataSet into a vtkPartitionedDataSet of its pieces, and a
dict of names to
PartitionedDataSets into a vtkPartitionedDataSetCollection whose
partitioned datasets carry their names (vtkCompositeDataSet.NAME()).
A structured topology over uniform coordinates gives a vtkImageData (with
the direction matrix), over rectilinear ones a vtkRectilinearGrid, over
explicit ones (a warped or transformed grid) a vtkStructuredGrid; an
unstructured one a vtkUnstructuredGrid, a polygonal or polyline one a
vtkUnstructuredGrid of VTK_POLYGON or VTK_POLY_LINE cells, and a
polyhedral one a vtkUnstructuredGrid of VTK_POLYHEDRON cells, each
keeping its own copy of its faces, outward for it (VTK's layout has no
orientation bit, so a shared face cannot be stored once).
H1 fields and values on points become point data -- an order-2 field
its values at the points, the grid being linear -- and Constant fields
and values on cells cell data. L2 fields have no VTK array to go
to without duplicating points, and fields on faces or edges none at
all; they are left out. The geometry (data.geometry) becomes the
points, so it must be H1. Implicit types, offsets and connectivity,
and constant and counting field values, become VTK's constant and affine
arrays. Ghost marks become vtkGhostType arrays.
The step's time (data.time) becomes VTK's DATA_TIME_STEP; VTK has
no unit for it.
Source code in packages/tack-vis/src/tack/interop/vtk.py
1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 | |
tack.interop.mfem ¶
MFEM meshes and grid functions as tack.dataset datasets, through PyMFEM.
mfem_to_dataset(mesh, fields) copies an mfem.ser.Mesh and named
GridFunctions on it. The mesh's elements are the topology, and its
element attributes an i32 Constant field, "attribute".
MFEM lists every element's corners in VTK's order, the prism included:
both put the triangle (0, 1, 2) with its normal toward (3, 4, 5), and a
positively oriented MFEM prism has a positive Jacobian as a VTK wedge, as
VTK's own wedges do. (MFEM's VTK writer swaps a prism's corners 1 and 2,
and 4 and 5 -- PrismMap in mesh/vtk.cpp -- which turns such a prism
inside out against VTK's documented and implemented convention.)
Each grid function becomes a field in the space its collection matches:
H1order 1 (H1_*_P1): anH1field, one value per vertex, which is how MFEM numbers an order-1 H1 space's DOFs;L2order 0: aConstantfield;L2order 1: anL2field of each element's values at its corners, where MFEM evaluates its own basis (its DOFs sit at interior Gauss points, not corners). On tetrahedra, hexahedra and prisms the two spaces are the same, so this is exact; on pyramids MFEM's order-1 L2 space is not the collapsed trilinear one, and the corners interpolate it;H1order 2: anH1order-2 field, MFEM's function evaluated at each element's quadratic nodes (corners, edge middles, quad-face middles, a hexahedron's center). MFEM's order-2 nodes are these same points, so it is exact; pyramids, whose MFEM basis is another, are refused.
A curved mesh -- nodes of order 2 -- gets an order-2 geometry the same way.
Vector grid functions (vdim > 1) become fields of vectors, in either
ordering. Higher orders are refused until their bases exist. PyMFEM
(pip install mfem) is imported on first use.
mfem_field ¶
mfem_field(data, mesh, gf, dtype=tack.f32)
gf, an MFEM grid function on mesh, as a field of data (the dataset
mfem_to_dataset made from mesh).
Source code in packages/tack-vis/src/tack/interop/mfem.py
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | |
mfem_to_dataset ¶
mfem_to_dataset(
mesh, fields=None, dtype=tack.f32, id_dtype=None
)
A tack.dataset.DataSet of an MFEM mesh and named grid functions on it.
The geometry is the vertex positions (z = 0 for a 2D mesh), or for a curved
mesh -- nodes of order 2 -- an H1 order-2 field of positions; nodes of
higher order are refused. Element attributes become fields["attribute"].
id_dtype is the topology's (tack.dataset.ids).
Source code in packages/tack-vis/src/tack/interop/mfem.py
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 | |
tack.interop.warp ¶
Arrays and datasets between Tack and NVIDIA Warp, without copies where they agree.
With the Warp device (tack.device("warp")) a Tack array's memory is a
wp.array on Warp's device, and Tack launches on that device's current
Warp stream, the stream a caller's wp.launch uses. So a program can run
its own Warp kernels and Tack's filters on the same arrays:
tack.set_default_device("warp")
wp.launch(my_kernel, n, inputs=[f])
data.fields["f"] = td.Field(td.H1(data), from_warp(f))
iso = alg.contour(data, "f", 0.5)
points = to_warp(iso.geometry.values) # a wp.array of wp.vec3
from_warp and to_warp map Warp's vector and matrix types to Tack's
(wp.vec3 and tack.vec(3, tack.f32), wp.mat33 and
tack.mat(3, 3, tack.f32)), keep the other side's memory alive for as
long as the result lives, and do not mark the memory as handed outside
Tack: on one device and stream, Tack's launches need not complete before
returning. Under wp.ScopedStream Tack follows the scope's stream too.
Warp arrays have no read-only flag: a read-only Tack array given to Warp can be written there.
to_simdata and from_simdata exchange datasets with warp-simdata
(NVIDIA's simulation-data library on Warp, an optional import): image
data, structured grids, polydata and unstructured grids (polyhedra
included) against Tack's topologies, NODE and ELEMENT fields against point
and cell fields, collections against partitioned datasets. Most of
warp-simdata's other models come across as they are, sharing their arrays
(see from_simdata); the rest through its to_vtk_data_model.
from_warp ¶
from_warp(array, *, writable=True)
array, a contiguous wp.array, as a Tack array on the Warp device,
sharing its memory: a write through either is seen by the other.
The element type follows Warp's (wp.vec3 gives tack.vec(3,
tack.f32)) and the shape is the array's. The Tack array keeps
array alive. array must be on the device Tack's Warp device
drives (Warp's default device).
Source code in packages/tack-vis/src/tack/interop/warp.py
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 | |
to_warp ¶
to_warp(array)
array as a wp.array sharing its memory, with Warp's element type
(tack.vec(3, tack.f32) gives wp.vec3) at its logical shape.
An array on the Warp device is its own memory, kept alive by the result.
An array on another Tack device goes through DLPack, which completes its
queued launches. An implicit array (tack.arrays: ConstantArray,
UniformCoordinates, ...) has no memory, and is materialized on its
device first. Structure-of-arrays storage has no single wp.array;
give its components one at a time.
Source code in packages/tack-vis/src/tack/interop/warp.py
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 | |
to_simdata ¶
to_simdata(data)
A Tack DataSet (or PartitionedDataSet) as a warp-simdata dataset (or
DatasetCollection) on Warp's device.
A uniform grid becomes image data (an origin, a spacing and an extent; a rotated one becomes a structured grid), any other structured grid a structured grid, polygons and polylines polydata, and every other topology an unstructured grid, polyhedra with their faces. Point fields become NODE fields and cell fields ELEMENT ones. Arrays already f32 positions, i32 ids and u8 types on the Warp device are shared; others are copied, positions as f32 world positions (Tack's origin added, so far coordinates lose the precision the origin kept). Ghost marks, global ids, sets, units and time have no counterpart there and are left behind.
Source code in packages/tack-vis/src/tack/interop/warp.py
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 | |
from_simdata ¶
from_simdata(ds)
A warp-simdata dataset (or DatasetCollection) as a Tack DataSet (or
PartitionedDataSet) on the Warp device, sharing its arrays.
Point clouds, curves, surface meshes, OpenFOAM polyMesh and boundary
patches, CGNS sections of linear elements (one shape, MIXED, NGON_n and
NFACE_n), EnSight parts of one kind, corner-point grids (inactive cells
hidden) and FLASH axisymmetric data (an AMRDataSet of revolved boxes)
are taken as they are: their arrays become Tack's, CGNS's 1-based and MIXED
connectivities read lazily (see "models imported as they are"). Every
other model goes through warp-simdata's to_vtk_data_model first. Image data becomes a uniform grid (offset from its extent), a structured
grid a structured topology over its points, polydata of polygons or
polylines a polygonal or polyline topology (vertices alone become vertex
cells; a mix of kinds is refused: split it first), and an unstructured grid
an unstructured topology, or a polyhedral one when it has polyhedra. NODE
fields become H1 fields and ELEMENT fields Constant ones. The dataset must
be on the device Tack's Warp device drives.
Source code in packages/tack-vis/src/tack/interop/warp.py
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 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 | |