Skip to content

Vis/field resolver - #300

Open
jlee-kitware wants to merge 14 commits into
Kitware:mainfrom
jlee-kitware:vis/field-resolver
Open

jlee-kitware wants to merge 14 commits into
Kitware:mainfrom
jlee-kitware:vis/field-resolver

Conversation

@jlee-kitware

@jlee-kitware jlee-kitware commented Nov 4, 2026 •

Copy link
Copy Markdown
Collaborator

One resolver for field arguments; evaluated fields through filters and writers

Stacked on #299

Problem
Fields computed where they are read, such as field expressions from tack-expr, need three things from Tack:

  • algorithms that take them by text;
  • filter outputs that keep them;
  • writers that store them.

Today a field argument is a Field or a stored name, filters carry such a field's placeholder values, and writers drop it.

Approach

  • register_field_resolver(resolve): a field argument that names no stored field goes to resolve(data, text), which returns a Field or None. A bare name that matches no field is still a KeyError. tack-expr registers a resolver, so td.contour(data, "mag(U)", 1.0) contours an expression. Tack's filters take strings through this hook, and core never parses them.
  • Filters carry a field's given gradient with it. An evaluated field is made again on the filter's output through its space's rebind, rather than having its values carried.
  • dataset_to_vtk writes an evaluated field's values through its space's stored.
  • An evaluated space may give default_subdivision, the number of lattice steps contour, clip and threshold cut its cells into. Contour shares points between cells for a continuous evaluated field.
  • Derived fields: normals, measures and centroids are each one name. They are read at the location the reduction or expression asks for (points, cells or faces), and a field stored under the name comes first. They are computed from the mesh alone, shared by every dataset on that mesh, and made again only when the mesh changes. So integrate(surface, ["dot(U, normals)"]) is a surface flux with no filter first, and over many time steps the normals are computed once.

Verified
tack-vis on CPU and CUDA, including tests for the resolver (contour and gradients by text, a missing name still a KeyError) and for a given gradient carried through threshold. tack-expr's tests exercise the evaluated-field paths.

A space can mark itself evaluated, read through its view's value and
corner_value alone with no stored values to trace: contour's subdivision then
computes it at each lattice point, and its exact crossings find the field's
roots through value. A space marked continuous shares crossings between cells
as H1 does. Together they let contour cut a field computed on demand, an
expression of other fields say, without storing it.
…ebind evaluated fields

Text that names no field goes to a registered resolver, so field expressions
work wherever a field does. A space evaluated where it is read gives its own
default subdivision, is made again on a filter's output, and is written as its
values.
A solver's derivatives are its own: a finite-volume reconstruction, or a
lattice Boltzmann solver's from its populations. Field(..., gradient=) takes
that gradient as a field on the same topology (gradients' layout), and
world_gradient and world_jet ask the field's view, which reads it, or a space's
own derivatives, or else differentiates the basis as before. gradients splits
a vector's given gradient into its rows, so flow_quantities and every kernel on
world_gradient use the solver's derivatives.
@berkgeveci berkgeveci added this to the 0.7 milestone Nov 4, 2026
jlee-kitware added a commit to jlee-kitware/tack that referenced this pull request Nov 4, 2026
…face

# Conflicts:
#	packages/tack-vis/tests/test_field_resolver.py
#	packages/tack-vis/tests/test_given_gradients.py
register_derived_field(name, compute, at) lets a name stand, at points, cells or
faces, for a field a dataset makes from itself where it stores none of that
name there; named_field(data, name, at) gives the stored one there first, then
the derived one, made once and kept while the geometry is the same. Every field
argument reads them. Tack registers normals (a surface's at its points and
cells, every mesh's at its faces), measures (cells and faces) and centroids.
Normals, measures and centroids are kept on the geometry, so every dataset
on the same geometry shares them. extract_surface of the same geometry keeps
its surface geometry, so a flux over many steps derives the normals once.
What is computed from the mesh alone (derived fields, the boundary's
geometry) is kept with it: shared by every dataset on the same geometry, and
made again when the mesh changes, by another geometry or positions Tack has
written since.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants