Skip to main content

DPF Framework 2026 R1

Field fundamentals

Last update: 31.07.2026

The Field is DPF's fundamental simulation data container. It is a data structure that associates numerical values with entities in a discretized region, along with metadata that makes the data self-describing. Every field contains interconnected components that define what the data represents, where it applies, how it is measured, and how it is structured.

Field architecture

A field is composed of the following components:

  • Data array: Holds the actual numerical values
  • Scoping: Defines which entities have data
  • Support: Provides the mesh and time/frequency context
  • Unit: Specifies the measurement system for the values
  • Field definition: Stores dimensionality information for interpreting the data array

Data array

The data is a flat one-dimensional array of numerical values (stored as double-precision floats). This array contains all the values in the field, organized in a specific ordering structure determined by the field's location and shell layer properties.

The data array ordering follows a nested loop structure from coarse to fine:

  1. Entity (outermost loop): Iterate through each entity in the scoping
  2. Node (for ElementalNodal location only): Iterate through nodes of each element
  3. Layer (for shell elements): Iterate through shell layers (bottom, top, mid)
  4. Component (innermost loop): Iterate through vector/tensor components

Example — 2 nodes, 3-component vector (displacement, no shell layers):

data = [X_node1, Y_node1, Z_node1, X_node2, Y_node2, Z_node2]

Example — 1 shell element, 6-component stress tensor, topbottom layers:

data = [
    # Element 1 – bottom layer
    σx_bot, σy_bot, σz_bot, τxy_bot, τyz_bot, τxz_bot,
    # Element 1 – top layer
    σx_top, σy_top, σz_top, τxy_top, τyz_top, τxz_top
]

Total length: 1 element × 2 layers × 6 components = 12 values.

Example — 1 shell element (4 nodes), ElementalNodal stress, topbottom layers:

data = [
    # Element 1, Node 1
    σx_n1_bot, σy_n1_bot, σz_n1_bot, τxy_n1_bot, τyz_n1_bot, τxz_n1_bot,  # bottom
    σx_n1_top, σy_n1_top, σz_n1_top, τxy_n1_top, τyz_n1_top, τxz_n1_top,  # top
    # Element 1, Node 2
    σx_n2_bot, ..., τxz_n2_bot,
    σx_n2_top, ..., τxz_n2_top,
    # … Node 3, Node 4 (same pattern)
]

Total length: 1 element × 4 nodes × 2 layers × 6 components = 48 values.

Elementary data

Elementary data is the set of values associated with a single entity. It represents the complete data for one node, one element, or one time step.

The size of elementary data depends on the field's location:

  • Nodal or Elemental: components × layers
  • ElementalNodal: nodes per element × components × layers

Structure by dimensionality:

  • Scalar: 1 value per entity (e.g., temperature: [21.5])
  • Vector: 3 values per entity (e.g., displacement: [2.5, -1.2, 0.8])
  • Tensor: 6 or 9 values per entity (e.g., stress: [σx, σy, σz, τxy, τyz, τxz])
  • ElementalNodal: All nodal values for the element (e.g., 4-node element with 3 components: 12 values)

ElementalNodal fields and element-type properties

For ElementalNodal fields, the size of each elementary data block depends on the element type of the entity it describes. Mixed-element meshes therefore produce variable-size ElementalNodal fields: each element contributes n_nodes_per_element × n_components × n_layers consecutive values, with no padding between elements.

To locate each entity's data block in the flat array without recomputing per-element sizes, every field exposes a data_pointer array: data_pointer[i] is the index in the flat data array where entity i's elementary data begins. For fields where every entity has the same number of components (typical Nodal, Elemental, scalar or 3D-vector cases) the data_pointer is empty - the simple stride index × elementary_data_size is sufficient. For variable-size fields (mixed-element ElementalNodal, connectivity property fields) it is populated and is the canonical way to slice the data array per entity without walking element types upfront.

The number of rows per element (n_nodes_per_element for ElementalNodal, 1 for Elemental) is derivable from the element type alone, independently of any specific field. This information lives on the meshed_region, not on the field. Two element-type properties are central to interpreting the data layout:

  • Shape (0D, 1D, 2D, 3D -> point, beam, shell, solid), exposed on the meshed region as the elshape property. It governs the geometric role of the element and which nodes carry physically meaningful results. (Note that dimensionality in DPF is a distinct, field-level concept - scalar, vector, tensor - and does not describe element geometry.)
  • Order. Second-order (quadratic) element types have mid-side nodes in addition to their corner nodes. This is a topology fact of the element type itself.

Shape and order are properties of the element type; they are not field-level concepts. The field's location only states where the data sits (Nodal, Elemental, ElementalNodal); it does not encode the element shape, order, or node count.

Whether an ElementalNodal field defined on a mesh with second-order elements actually contains rows for those mid-side nodes is a separate, field-level property. It is recorded as a boolean flag on the field definition (has_midnode_data in the C++ API). Two ElementalNodal fields can share the same support and still differ in how much data they store per second-order element: one may carry only corner-node rows, another the full corner + mid-node layout. See Mid-node values and extend_to_mid_nodes below for how to move between the two.

Entity ID uniqueness across element types

Element IDs used in the scoping of an Elemental or ElementalNodal field are globally unique within the MeshedRegion, across all element types. A given integer ID identifies exactly one element regardless of whether it is a tetrahedron, hexahedron, shell, beam, or point. The same uniqueness guarantee applies to node IDs in Nodal scopings. The scoping's ID list is therefore sufficient on its own to locate each entity in the mesh - no element-type lookup is needed to disambiguate IDs.

Mid-node values and extend_to_mid_nodes

When an ElementalNodal field is read from a solver file that produced results at both corner and mid-side nodes, the field already contains one row per node per element - corner and mid-node rows interleaved per element in the order declared by the element's connectivity.

Solver files that only store corner-node values for second-order elements produce an ElementalNodal field with corner-node rows only. To obtain a field that also carries mid-node rows for downstream consumers that expect them, use the extend_to_mid_nodes operator (or extend_to_mid_nodes_fc for a fields container). It interpolates corner-node values onto the mid-side nodes, producing a field whose elementary data sizes match the full corner + mid-node layout.

Homogeneous sub-fields per element type

When downstream code needs to process the data array as a regular block - for example reshaping it as (n_entities, rows_per_entity * components) - the variable-size layout of a mixed-element ElementalNodal field becomes an obstacle. The recommended strategy is to split by element type: use the split_on_property_type operator with the eltype property to produce one scoping per element type (triangles, quads, tets, hexes, ...). Re-evaluating (or rescoping) the result for each scoping yields homogeneous-size sub-fields, one per element type, that can each be processed as a regular block without per-element bookkeeping. Splitting on the coarser elshape property (solid, shell, beam, point) is not sufficient on its own: a single shape class can still mix element types with different node counts (for example tet4 and hex8 are both solids).

Unit

The unit defines the measurement system for all values in the field. All data in a field shares the same unit.

Common units: m (meters), Pa (Pascals), °C (Celsius), s (seconds), kg (kilograms)

Location

The location specifies what kind of entities the field's data is associated with within the support.

Standard locations:

Location Description Typical use
Nodal Data at mesh nodes (vertices) Displacement, temperature
Elemental Data at mesh element centers Element volumes, material IDs
ElementalNodal Data at each node of each element Stress, strain (extrapolated to nodes)
TimeFreq Data at time steps or frequencies Global quantities over time
Overall Data for the entire model (single value) Total mass, global energy

Scoping

The scoping of the field defines which specific entities have data in the field by listing their IDs. It acts as an index mapping between the data array position and the actual entity identifiers.

Structure: An ordered list of entity IDs (e.g., [10, 25, 100, 533] for node IDs)

Key properties:

  • Length: Number of entities with data (scoping.size)
  • IDs: The actual entity identifiers from the mesh/support (potentially non-sequential)
  • Index mapping: Position in the scoping (0-based) vs. entity ID (1-based or non-sequential)

Why scoping is essential: It allows fields to store data for subsets of a model rather than requiring data for all entities. For example, a boundary condition field might only contain data for nodes on a specific surface.

Support

The support defines the region where the field's data applies. It provides the spatial, temporal, or structural context for interpreting the entities.

Support types:

Support type Purpose Example
MeshedRegion Physical space (3D geometry) Finite element mesh
TimeFreqSupport Temporal/frequency domain Time steps 0s to 10s
CyclicSupport Cyclic symmetry structures Turbine with repeated sectors

The support is referenced by the field but not owned by it - multiple fields can share the same support.

Field definition

The field definition aggregates metadata properties about the field: its dimensionality (number of components), unit, location, and shell layer configuration. It is accessed as a single object containing these interconnected properties.

Shell layers

Shell elements model thin-walled structures (plates, sheet metal, composite panels) by collapsing physical thickness into a 2D surface mesh. Because stresses and strains vary through the thickness, results can be stored at several positions — called layers — through the wall. The shell_layers property on a field records which of those positions are present.

Shell layer options:

Value Description Use case
layerindependent No layer information Solid elements, or results that do not vary through thickness (e.g. displacement)
bottom Bottom surface only Single-sided result
top Top surface only Single-sided result
topbottom Bottom and top surfaces Standard shell stress/strain
mid Mid-surface only Membrane behaviour
topbottommid Bottom, top, and mid surfaces Detailed through-thickness analysis

Effect on the data array

When layers are present, the flat data array gains an extra nesting level. The ordering rules are:

  • Nodal / Elemental location: entity → layer → component
  • ElementalNodal location: entity (element) → node → layer → component

For a field with layerindependent (or for solid-element results), the layer loop collapses to a single pass, so the ordering reduces to the standard entity → component (or entity → node → component for ElementalNodal).

Elementary data size:

  • Nodal / Elemental with layers: components × number_of_layers
  • ElementalNodal with layers: nodes_per_element × components × number_of_layers

Return shape from entity-level access

When layers are present, get_entity_data() and get_entity_data_by_id() return a 2D array shaped (number_of_layers, components) rather than a 1D array. The layer order in the returned array matches the order declared by shell_layers (typically bottom → top → mid for topbottommid).

Example — 6-component stress tensor, topbottommid layers, entity ID 100:

# Returned shape: (3, 6)
[
  [σx_bot, σy_bot, σz_bot, τxy_bot, τyz_bot, τxz_bot],   # row 0: bottom
  [σx_top, σy_top, σz_top, τxy_top, τyz_top, τxz_top],   # row 1: top
  [σx_mid, σy_mid, σz_mid, τxy_mid, τyz_mid, τxz_mid]    # row 2: mid
]

For a scalar field (layerindependent), the return is always a 1D array with a single value [value], unchanged from the no-layer case.

Name

The name is an optional text identifier for the field, used for documentation and debugging purposes. Examples: "displacement", "stress_von_mises", "temperature_field".

Field variants

While the standard Field stores double-precision floating-point data, DPF provides specialized field types for different data types:

PropertyField

Stores integer values instead of doubles. Used for:

  • Material IDs or element type IDs
  • Boolean flags (represented as 0/1)
  • Element or node property indices
  • Mesh connectivity information

StringField

Stores text values. Used for:

  • Material names or labels
  • Result descriptions
  • Custom annotations

CustomTypeField

Stores custom data types beyond standard primitives. Used for:

  • Complex numbers (electromagnetic analysis)
  • Custom types defined by specialized plugins
  • Any data type compatible with numpy.dtype

All field variants follow the same organizational structure (data, scoping, support, location) but differ in the data type stored in the data array.

Data access patterns

How you read field data efficiently depends on two factors: what scope of data you need (a handful of entities vs. the whole field) and where the DPF server is running relative to your client code.

Entity-by-entity access

Two methods let you query data for a single entity at a time:

get_entity_data(index): Retrieve data by position (0-based index) in the scoping.

  • The index is the ordinal position in the scoping list, not the entity ID.
  • Use when iterating sequentially through all entities — though see the loop warning below.

get_entity_data_by_id(id): Retrieve data by entity ID (the actual node or element ID from the mesh).

  • The id is the entity identifier from the support (1-based, potentially non-sequential).
  • Use when you need data for a small set of known entities.

Index vs. ID: Because scoping IDs are not necessarily sequential (e.g., scoping [10, 25, 100] has index 0 → ID 10, index 1 → ID 25), the two methods serve distinct needs. Conversions are available through the scoping object (scoping.id(index) and scoping.index(id)).

Return shape: The returned array is automatically shaped based on the field's component count and shell layers:

  • Scalar field (1 component): [value]
  • Vector field (3 components): [X, Y, Z]
  • Tensor field with layers (6 components, 3 layers): [[σx_bot, ...], [σx_top, ...], [σx_mid, ...]]

Loop anti-pattern: Do not call entity-by-entity methods inside a loop over many entities. Each call is a round-trip to the DPF server. With thousands of entities this becomes prohibitively slow, regardless of whether the server is local or remote. Use bulk access instead (see below).

Bulk data access

field.data: Returns the complete flat data array.

  • On a local server, this returns a direct pointer to server memory — no data is copied, making it the fastest option for any field size.
  • On a remote server, this transfers the entire array over the network in a single operation.

as_local_field(): Downloads the field from a remote server into local memory and wraps it in a context manager. All subsequent get_entity_data() calls within the context block operate on the local copy (local array indexing, no further network traffic). This approach also ensures proper resource cleanup when the context exits.

Choosing the right approach

The table below summarises which pattern to use based on server topology and field size:

Scenario Recommended approach Rationale
Local server, any field size field.data + manual index arithmetic Direct memory pointer, zero copy overhead
Remote server, small-to-medium field field.data or as_local_field() Single network transfer, then local operations
Remote server, large field (> ~100 MB) as_local_field() Controlled download with explicit cleanup
Remote server, very large field (> ~1 GB) Filter server-side first, then download the subset Avoids transferring data that will not be used
Only a few specific entities needed get_entity_data() or get_entity_data_by_id() Sufficient for small query counts; avoid in tight loops

Filtering server-side before downloading

When only a subset of entities is needed from a large field on a remote server, the most efficient pattern is to reduce the data on the server first — before any network transfer occurs. Two common mechanisms are:

  • Mesh scoping: Provide a scoping to the result operator (or to a rescope operator) so that only data for the relevant nodes or elements is evaluated and returned.
  • Time/frequency scoping: Request only the time sets or frequencies you need rather than all sets.

This server-side filtering can reduce a gigabyte-scale transfer to a megabyte-scale one, after which the normal field.data or as_local_field() patterns apply to the smaller result.

Properties summary

Key field properties for introspection:

Property Description Type
location Type of entities (Nodal, Elemental, etc.) str
component_count Number of components per elementary data int
elementary_data_count Number of elementary data (entities) int
size Total data array length (entities × layers × components) int
scoping Entity IDs with data scoping
unit Measurement unit str
shell_layers Shell layer configuration enum
field_definition Aggregated metadata field_definition
data Raw data array array of double

Common pitfalls

Replacing the scoping does not resize the data buffer

Assigning a new scoping to a field updates only the metadata - it does not filter or resize the underlying data array. After reassignment, the scoping length and the data length may be inconsistent, which leads to silent incorrect results or errors when the field is consumed by a downstream operator.

Do not use the scoping setter to extract a spatial subset. Instead:

  • Use the rescope operator to produce a correctly-sized field for a chosen subset.
  • Call field.deep_copy() to obtain an independent copy before modifying the scoping.
import ansys.dpf.core as dpf

model = dpf.Model("file.rst")
result_field = model.results.displacement()[0]
my_scoping = dpf.Scoping(ids=[1, 2, 3], location=dpf.locations.nodal)

# Correct - use rescope to filter any field to a scoping
rescope_op = dpf.operators.scoping.rescope()
rescope_op.inputs.fields.connect(result_field)
rescope_op.inputs.mesh_scoping.connect(my_scoping)
filtered_field = rescope_op.outputs.fields_as_field()

# Correct - deep-copy a field before modifying its scoping
my_field = result_field.deep_copy()

coordinates_field returns a live reference

The MeshedRegion.nodes.coordinates_field property (Python nodes.coordinates_field, C# or IronPython MechDPF MeshedRegion.CoordinatesField) returns a live reference to the internal mesh node coordinate array. Any modification to the returned field is reflected immediately in the mesh, and subsequent calls return the same underlying data.

To obtain an independent snapshot that is safe to modify without affecting the mesh:

# Python - deep copy
coords_copy = model.mesh.nodes.coordinates_field.deep_copy()

In C# or IronPython (MechDPF), call GetHardCopy() to obtain an independent copy:

// C# - independent copy
Field snapshot = mesh.CoordinatesField.GetHardCopy();

Fields are often used in combination with:

Connect with Ansys