Section at 40% semi-span · chord framex/c →
|V|/V∞ 0.41.6 V∞
surrogatemodels

Everything starts as tokens.

In one lineEach mesh point becomes a vector of numbers describing where it is and what it carries.

You will learn to
  • Turn mesh points into input features: position, normal, flow condition, Fourier features
  • Explain an embedding: an MLP that lifts each point's features to d numbers
  • Build a token tensor of shape (N, d) in PyTorch

A network cannot read a mesh. Each point (or patch, or cell) is turned into a token: a vector of numbers that describes where it is and what it carries.

You have already built tokens without the name. The neural field took one surface point, its position and the flow condition (α, Λ), turned the position into Fourier features, and fed the result to an MLP. A token is the same idea made explicit: one vector of d numbers per point (or per patch, or per cell), the unit that the models of this unit pass around.

From raw features to an embedding

The raw features of a point are the numbers you know: x, y, z, the surface normal, the distance to the wall, the flow condition, and Fourier features of the position (sines and cosines at several frequencies, as in the neural-field lesson). An embedding is a small MLP applied to every point separately that maps these raw features to d numbers, typically 64 to 512. Nothing is shared between points yet: the embedding only puts every point into the same learned d-dimensional space, where the next layers can compare and mix them.

PyTorch · 8,000 points become 8,000 tokens
import torch
from torch import nn

N, d = 8000, 128                 # surface points, token size
xyz = torch.rand(N, 3)           # positions, normalised
normal = nn.functional.normalize(torch.randn(N, 3), dim=-1)
# alpha = 4 deg and sweep = 28 deg, normalised as in the
# neural-field lesson, the same for every point.
cond = torch.tensor([(4.0 - 3) / 5, (28.0 - 28) / 8])
cond = cond.expand(N, 2)

# Fourier features with 12 random frequencies.
B = torch.randn(3, 12) * 4.0
fx = xyz @ B
raw = torch.cat([xyz, normal, cond, fx.sin(), fx.cos()],
                dim=-1)          # (N, 32)

# The embedding: one small MLP applied to every point.
embed = nn.Sequential(nn.Linear(raw.shape[-1], d),
                      nn.SiLU(), nn.Linear(d, d))
tokens = embed(raw)              # (N, d): one token per point
print(raw.shape, tokens.shape)   # (8000, 32) (8000, 128)

In practice, normalise the raw features first (normalising), as the snippet does for α and Λ, and choose the Fourier frequencies to match the smallest feature you need to resolve. The neural-field lesson used fixed frequencies doubling at each octave (as in NeRF); here they are drawn at random, the other option studied by Tancik and colleagues. Both work; the scale of the frequencies matters more than how they are chosen.

Where a token is: positional encodings

The attention layers of the next lessons mix tokens without knowing where they are, unless the tokens say so. Any way of putting position into the token is a positional encoding. Coordinates with Fourier features, as above, are the usual choice for meshes and point clouds. Language models and several recent surrogates use rotary position embeddings (RoPE) instead: rather than adding position to the token, they rotate each query and key by angles that depend on position, so the attention score between two tokens depends on their relative position. Grid models make one token per patch of cells; CAD-native models make one per surface patch (the CAD lesson).

Check yourself

What is the difference between a point's raw features and its token?

Raw features are the numbers you know (x, y, z, normal, α). The token is what a learned MLP makes of them: d numbers in a space the rest of the network can use.

Why repeat α and Λ in every token when they are the same for the whole case?

Each token is processed partly on its own, so each one must carry the flow condition. Some models inject it once as an extra token instead.

Next lessonSelf-attention and the transformer


Self-attention: every token looks at every other.

In one lineEvery token looks at every other token and decides what matters to it.

You will learn to
  • Compute queries, keys and values as three linear maps of each token
  • Define softmax and read an attention-weight matrix
  • Assemble a transformer block: attention, MLP, residuals and layer norm
  • Explain the O(N²) cost and how attention differs from message passing

Each token asks a question (a query), advertises what it contains (a key) and offers information (a value). It then gathers the values of the tokens whose keys best match its query.

A token on its own knows only its own point. To predict the pressure at the trailing edge, the model has to use what happens at the leading edge, the tip and the root. Self-attention lets every token gather information from every other token in one step, with weights it computes from the tokens themselves.

Queries, keys and values

Each token x is multiplied by three learned matrices, giving three new vectors: a query q = x·W_Q (what this token is looking for), a key k = x·W_K (what it offers to be found by) and a value v = x·W_V (what it passes on). They are three linear layers without bias, applied to every token. The match between token i and token j is the dot product of i's query with j's key, divided by √d to keep the numbers in a sensible range.

The scores are turned into weights with softmax: exponentiate each score and divide by the sum, so the weights are positive and add up to 1. Scores [2, 1, 0] become weights [0.67, 0.24, 0.09]. Each token's output is the weighted sum of all values.

Attention(Q, K, V) = softmax(Q·Kᵀ / √d_k) · V
Q, K, V: the queries, keys and values of all N tokens stacked as rows; d_k: the size of each query and key (d/h per head). softmax acts on each row. The result has one row per token.
PyTorch · attention in six lines, then the built-in
import torch
import torch.nn.functional as F

N, d = 1000, 64
x = torch.randn(N, d)                      # N tokens
Wq, Wk, Wv = (torch.randn(d, d) / d**0.5 for _ in range(3))

q, k, v = x @ Wq, x @ Wk, x @ Wv           # three linear maps
w = torch.softmax(q @ k.T / d**0.5, dim=-1)  # (N, N)
out = w @ v                                # (N, d)

# The built-in does the same without storing w.
same = F.scaled_dot_product_attention(q[None], k[None],
                                      v[None])[0]
print(torch.allclose(out, same, atol=1e-5))  # True

The figure beside the text uses toy numbers: eight regions with hand-made two-dimensional embeddings, so you can see a weight matrix and how one row reads. In a trained model, d is 64 to 1,024 and the weights are learned. The race-car view in the industrial example is a qualitative illustration of the same idea, not a computed result.

Several heads

One set of weights can express one kind of relation. Multi-head attention runs h attention operations in parallel, each with its own smaller W_Q, W_K, W_V (size d/h), and concatenates their outputs. One head might follow the flow direction, another pair upper and lower surfaces. The cost is about the same as one head of full size.

The transformer block

A transformer is a stack of identical blocks. Each block does two things, each wrapped in a residual connection (add the result to the input, as in graph networks) and preceded by layer normalisation, which rescales each token's d numbers to zero mean and unit variance so that deep stacks train stably:

  1. multi-head self-attention, where tokens exchange information;
  2. an MLP applied to every token separately, where each token processes what it gathered.
PyTorch · one transformer block
import torch
from torch import nn

class Block(nn.Module):
    def __init__(self, d=128, heads=8):
        super().__init__()
        self.n1, self.n2 = nn.LayerNorm(d), nn.LayerNorm(d)
        self.attn = nn.MultiheadAttention(
            d, heads, batch_first=True)
        self.mlp = nn.Sequential(nn.Linear(d, 4 * d),
                                 nn.SiLU(), nn.Linear(4 * d, d))

    def forward(self, x):                  # x: (B, N, d)
        h = self.n1(x)
        # Tokens exchange information...
        a = self.attn(h, h, h, need_weights=False)[0]
        x = x + a
        # ...then each token is updated on its own.
        return x + self.mlp(self.n2(x))

print(Block()(torch.randn(2, 1000, 128)).shape)

The catch: N²

The weight matrix has N × N entries. For the aircraft's 8,000 points that is 64 million per head, manageable. For a car surface with 1,000,000 points it is 10¹², about 2 TB in 16-bit numbers for one head of one layer. Memory-efficient kernels such as the one behind scaled_dot_product_attention avoid storing the matrix, but the compute still grows as N². The rest of this unit is about getting around it.

Check yourself

What does softmax do to the scores [2, 1, 0]?

It exponentiates and normalises them: about [0.67, 0.24, 0.09]. All positive, summing to 1, with the largest score getting the largest weight.

How many attention weights does one head compute for 100,000 tokens?

100,000² = 10¹⁰. Stored at 2 bytes each that is 20 GB for one head of one layer. Fused kernels avoid storing it, but the 10¹⁰ multiply-adds remain, which is why plain attention does not reach industrial meshes.

One layer of message passing reaches one hop. How far does one layer of self-attention reach?

Every token, in one layer. The price is the N² cost and the loss of the mesh's built-in notion of neighbours.

Next lessonCross-attention


Cross-attention: one set reads another.

In one lineA small set of queries reads from a large set of points, so cost stays linear.

You will learn to
  • Explain cross-attention: queries from one set, keys and values from another
  • Summarise N points into M learned latent tokens at a cost of N × M
  • Read a field out at arbitrary query points

In cross-attention, the queries come from one set of tokens and the keys and values from another. Surrogates use it twice: to read the geometry in, and to read the field out.

In self-attention the queries, keys and values all come from the same tokens. In cross-attention the queries come from one set and the keys and values from another. The formula is unchanged; only the inputs differ. If there are M queries and N keys, the weight matrix is M × N.

Reading in: a few latent queries

Take M = 256 learned vectors, the latent queries, and let them attend to all N mesh tokens. Each latent becomes a weighted summary of the points it chose to read. The cost is N × M instead of N²: for a million points, 2.6 × 10⁸ weights instead of 10¹². This is the Perceiver pattern, named after a general-purpose model (Jaegle and colleagues, 2021) that used it to read images, audio and point clouds into a small fixed set of latents.

PyTorch · 50,000 points summarised into 256 latent tokens
import torch
from torch import nn

B, N, M, d = 2, 50_000, 256, 128
# N point tokens: the keys and values.
points = torch.randn(B, N, d)
# M learned queries, M much smaller than N.
latents = nn.Parameter(torch.randn(M, d) * 0.02)
xattn = nn.MultiheadAttention(d, num_heads=8,
                              batch_first=True)

q = latents.expand(B, M, d)     # same queries for each case
z, w = xattn(q, points, points)
# Weights w: (B, M, N), not (N, N).
print(z.shape, w.shape)         # (2, 256, 128) (2, 256, 50000)

Reading out: any point is a query

The same mechanism works in reverse. To know the field at a location, embed its coordinates into a token and use it as a query that attends to the latent tokens. The query can be any point: on the surface, in the volume, on a mesh the model never saw in training. This is how many current latent surrogates produce their output (Transolver, in a later lesson, decodes point by point instead), and it is the subject of decoders.

Because inputs and outputs are just sets of points, such a model can be trained on one mesh and evaluated on another. That is the same resolution independence the neural operators lesson discussed, with the same caveat: it runs on any mesh, but accuracy still depends on what the training data covered.

Check yourself

N = 1,000,000 points, M = 256 latents. Compare the weights of self-attention over the points and of cross-attention into the latents.

Self-attention: 10¹² weights. Cross-attention: 2.56 × 10⁸, about 4,000 times fewer.

In a cross-attention decoder, where do the queries come from?

From the output locations: each point where you want the field is embedded (from its coordinates) and becomes a query reading from the latent tokens.

Next lessonLatent space


Compress to a latent space.

In one lineCompress millions of points into a few hundred learned tokens that summarise the case, and work there.

You will learn to
  • Relate a learned latent space to POD, its linear ancestor
  • Build an encode–process–decode surrogate with cross- and self-attention
  • Explain why latent rollouts are cheap for time-dependent problems

Industrial meshes have millions of cells. Modern surrogates encode them into a small, fixed set of latent tokens, do the heavy computation there, and decode back to any point.

You have met this idea before in linear form. POD writes a field as a mean plus a weighted sum of a few modes, so a whole Cp distribution is summarised by, say, ten coefficients. A surrogate then only has to predict ten numbers. A latent space is the learned, nonlinear version: an encoder compresses the input into M tokens of d numbers, the model works there, and a decoder reconstructs the field.

Encode, process, decode

  1. Encode: embed the N points as tokens, then cross-attention from M learned latent queries (the previous lesson). Cost N × M.
  2. Process: a stack of transformer blocks on the M latent tokens only. Cost M², independent of the mesh.
  3. Decode: each output location becomes a query that cross-attends to the latent tokens. Cost Q × M for Q query points.
qqqmillions of pointslatentany query
Millions of points are encoded into a fixed set of latent tokens, processed there, and decoded at any query point.
PyTorch · an encode–process–decode surrogate
import torch
from torch import nn

class LatentSurrogate(nn.Module):
    def __init__(self, f_in, d=128, M=256, L=4, heads=8):
        super().__init__()
        self.embed = nn.Linear(f_in, d)   # points to tokens
        self.query = nn.Linear(3, d)      # coords to queries
        self.latents = nn.Parameter(torch.randn(M, d) * 0.02)
        mha = lambda: nn.MultiheadAttention(
            d, heads, batch_first=True)
        self.enc, self.dec = mha(), mha()
        layer = nn.TransformerEncoderLayer(
            d, heads, 4 * d, batch_first=True, norm_first=True)
        self.process = nn.TransformerEncoder(
            layer, L, enable_nested_tensor=False)
        self.head = nn.Linear(d, 1)

    def forward(self, feats, xyz_out):
        # feats: (B, N, f_in); xyz_out: (B, Q, 3)
        t = self.embed(feats)
        lat = self.latents.expand(len(t), -1, -1)
        z = self.enc(lat, t, t)[0]        # encode: (B, M, d)
        z = self.process(z)               # process M tokens
        q = self.query(xyz_out)
        # Decode at any points: (B, Q, 1)
        return self.head(self.dec(q, z, z)[0])

model = LatentSurrogate(f_in=8)
feats, xyz = torch.randn(2, 20_000, 8), torch.rand(2, 5_000, 3)
print(model(feats, xyz).shape)            # (2, 5000, 1)

Time in latent space

For transient problems, the processor can predict the next latent state from the current one, and the rollout (time stepping and rollouts) happens entirely in latent space. Decoding is needed only at the times you want to look at, which makes long rollouts much cheaper than stepping the full mesh.

Further on this site: UPT on Papers. Emmi AI, the company behind UPT and AB-UPT, is in the news: Mistral AI acquires Emmi AI.

Check yourself

How is a POD reduced-order model like a latent transformer?

Both compress a field into a few numbers, work there, and reconstruct the field. POD compresses with fixed linear modes from an SVD; the transformer learns a nonlinear encoder and decoder.

Why is the processor's cost independent of the mesh size?

It only sees the M latent tokens. The mesh size enters the encoder and decoder, which cost N × M, not N².

Next lessonAttention at scale: Transolver


Unit 6 · Tokens, attention and latents/At industrial scaleAdvanced

Making attention scale to millions of points.

In one lineAttention grows with the square of the points; slices, latents and linear schemes keep it tractable.

You will learn to
  • Explain Transolver's physics attention: slice, attend among slices, deslice
  • Write the slice and deslice steps in PyTorch
  • Compare linear-cost schemes: slices, latents, linear attention, anchors
  • Read the ablation evidence on what makes Transolver work

Plain attention is quadratic. Recent architectures change what attends to what, so cost grows linearly with mesh size.

Plain attention costs N². Latent models (previous lesson) avoid it by compressing first. Transolver (Wu and colleagues, 2024) takes another route that has become a standard baseline for industrial meshes: group the points by physical state, and let the groups attend to each other.

Physics attention, step by step

  1. Slice. A linear layer gives every point a score for each of M slices (M = 32 or 64); a softmax over the M scores turns them into weights that sum to 1. A point in the stagnation region might belong 80 % to one slice, a point in the wake mostly to another. Slices are not regions of space: points far apart in a similar flow state share a slice.
  2. Slice tokens. Each slice token is the weighted mean of all point features, weighted by membership. N points become M tokens. Cost N × M.
  3. Attend. Ordinary multi-head self-attention among the M slice tokens. Cost M², tiny.
  4. Deslice. Each point receives the mix of slice tokens given by its own weights. Cost N × M again.
mesh points → statesslices attend
Transolver: points are softly assigned to a few learned slices of similar physical state, and attention runs among the slices.
PyTorch · slice, attend, deslice
import torch
from torch import nn

class PhysicsAttention(nn.Module):
    def __init__(self, d=128, M=64, heads=8):
        super().__init__()
        # A score per point for each of the M slices.
        self.to_slices = nn.Linear(d, M)
        self.attn = nn.MultiheadAttention(
            d, heads, batch_first=True)

    def forward(self, x):                 # x: (B, N, d)
        # (B, N, M): each point's weights sum to 1.
        w = self.to_slices(x).softmax(dim=-1)
        # Slice tokens: weighted means of points, (B, M, d).
        s = w.transpose(1, 2) @ x
        s = s / (w.sum(dim=1)[..., None] + 1e-6)
        # Attention among the M slice tokens only.
        s = self.attn(s, s, s, need_weights=False)[0]
        # Deslice: back to every point, (B, N, d).
        return w @ s

x = torch.randn(2, 100_000, 128)
print(PhysicsAttention()(x).shape)        # (2, 100000, 128)

A Transolver model is a stack of transformer blocks in which this layer replaces ordinary self-attention. The real implementation adds multiple heads for the slicing and a learnable temperature in the softmax, which controls how sharply points are assigned.

Million-point meshes and what really matters

Transolver++ (Luo and colleagues, 2025) sharpens the slice assignment locally and splits the points of one mesh across several GPUs, which only needs the small slice tokens to be shared, reaching meshes of over a million points. Other linear-cost routes include linear attention (GNOT and others), which replaces the softmax with a normalisation applied to queries and keys separately, so the product (Q·Kᵀ)·V can be regrouped as Q·(Kᵀ·V), a small d × d matrix, and the N × N matrix is never formed; latent compression (UPT); and anchor points (AB-UPT, in decoders).

Two descendants appear in the news. GeoTransolver (NVIDIA) adds cross-attention from the slices to a shared description of the geometry and the operating condition, reused in every block. FLARE routes attention through a small set of latent tokens in each layer, with two standard attention calls, and scales to million-point meshes on one GPU. Both became stable models in PhysicsNeMo 26.08.

A 2026 study asked which part of Transolver does the work. Replacing the attention among slices with a fixed linear map kept the accuracy on nine 3D fluid benchmarks, while removing the slicing and deslicing made it collapse. The global averaging through slices, not the attention, carries most of the benefit. The authors connect this to averaging neural operators, a theory showing that mixing every point with a few learned averages over the whole domain is already enough for a neural operator to approximate a wide class of PDE solutions. That is the kind of result an ablation gives, and it is the worked example of the last lesson, reading a surrogate paper.

Further on this site: Transolver, Transolver++ and Does Transolver really need a Transformer? on Papers.

Check yourself

With N = 1,000,000 points and M = 64 slices, how many slice weights per layer, and how many attention weights?

N × M = 6.4 × 10⁷ slice weights, and M² = 4,096 attention weights among the slice tokens. Plain attention would need 10¹².

What is a slice token?

A weighted average of all point features, with each point weighted by how strongly it belongs to that slice. Points in a similar physical state share slices wherever they are.

What did replacing slice attention with a fixed linear map show?

Accuracy barely changed on nine 3D fluid benchmarks, while removing slicing and deslicing hurt badly: the global averaging through slices does most of the work.

Next lessonDecoders


Unit 6 · Tokens, attention and latents/At industrial scaleAdvanced

Decoding the field, with physics in mind.

In one lineTurn latent tokens into surface pressure, shear or volume flow at any query point.

You will learn to
  • Recognise DeepONet's trunk as the first query decoder
  • Explain anchors: expensive attention on a few points, cheap reads for millions
  • Choose between shared and separate surface and volume branches

The decoder turns latent tokens into numbers engineers use: surface pressure, wall shear, velocity in the volume. Its design decides accuracy where it matters.

Whatever happens inside, a surrogate must end with numbers an engineer can use: pressure and shear on the surface, velocity in the volume. The decoder produces them. Most current models use a query decoder: give it a coordinate, and it returns the field there, reading from the model's internal state.

The first query decoder: DeepONet's trunk

DeepONet's trunk was already a query decoder: a neural field whose output the branch weights for each case. A cross-attention decoder (cross-attention) generalises it: each location reads different parts of the internal state instead of one fixed weighting.

Anchors: attention where it pays

AB-UPT (Emmi AI, 2025) splits the points in two. A few thousand anchor points, sampled from the mesh, carry the expensive attention among themselves. The remaining millions of query points attend only to the anchors, so their cost is linear in their number and they can be processed in chunks. Separate branches handle surface and volume, which need different resolutions, while sharing the geometry encoding.

PyTorch · anchors attend to each other, queries read the anchors
import torch
from torch import nn

B, N, A, d = 1, 200_000, 4_000, 128
# All surface and volume points, embedded as tokens.
tokens = torch.randn(B, N, d)
idx = torch.randperm(N)[:A]       # A random anchor points
anchors = tokens[:, idx]          # (B, A, d)

mha = lambda: nn.MultiheadAttention(d, 8, batch_first=True)
self_attn, cross_attn = mha(), mha()
# Pressure and three velocity components.
head = nn.Linear(d, 4)

# A x A attention among the anchors only.
a = self_attn(anchors, anchors, anchors,
              need_weights=False)[0]
anchors = anchors + a
# Any points we want answers at read the anchors: Q x A.
queries = tokens[:, :50_000]
r = cross_attn(queries, anchors, anchors,
               need_weights=False)[0]
print(head(r).shape)              # (1, 50000, 4)

Physics in the output

The decoder is also where physical laws can be built in. Predicting a stream function or vector potential and differentiating it gives a velocity field with exactly zero divergence, so in incompressible flow mass is conserved by construction. AB-UPT offers such a divergence-free volume formulation. How to do it, and when it is worth it, is the subject of physics in the loss and in the model.

Further on this site: AB-UPT and DeepONet on Papers. CANTO uses a query decoder for both surface and volume fields: CANTO in the news.

Check yourself

Why can a query decoder predict at points that were not in the training mesh?

The query is just a coordinate turned into a token. The decoder reads the latent state for that coordinate, like a neural field conditioned on the case.

AB-UPT: 16,000 anchors, 10 million queries. Where is the quadratic cost?

Only among the anchors (16,000², about 2.6 × 10⁸). Each query attends to the anchors only, so the queries cost 10⁷ × 16,000, linear in their number, and they can be processed in chunks.

Next lessonSurface and volume fields


Unit 6 · Tokens, attention and latents/At industrial scaleAdvanced

Surface and volume fields.

In one linePredict the field everywhere in the domain, not just on the walls: velocity, pressure, temperature in the volume.

You will learn to
  • Say why volume fields are harder to learn than surface fields
  • Compare surrogate and solver in two flow planes, inside and outside the training range
  • Connect volume prediction to query decoders and separate branches
Before this

Decoders

Surface quantities (pressure, shear, heat flux) give the loads; volume quantities (velocity, pressure, temperature, vorticity) explain them: wakes, vortices, separation, mixing. Volume prediction is harder: a 3D simulation has 10–1000× more cells than surface points, the regions that matter are small, and the model must be queried anywhere in space.

  • Query decoders (previous lesson): predict at any coordinate from a latent state, so surface and volume come from one model.
  • Separate branches for surface and volume outputs, each at its own resolution, as in AB-UPT.
  • Physics in the output, e.g. divergence-free velocity, to keep volume fields consistent (physics constraints, next unit).

Check yourself

Why is volume data the main cost of an industrial training set?

A 3D run has 10 to 1,000 times more volume cells than surface points, so the volume fields dominate storage, loading and training time.

Name two places where a volume surrogate typically has its largest errors.

Thin shear layers and wakes, and vortex cores: small regions with steep gradients, often under-represented when points are sampled uniformly.

Next lessonChoosing a model


Choose a model.

In one lineWhich family for which data and question, and a sensible baseline to try first.

You will learn to
  • Match a model family to the data structure, mesh size and question
  • Pick a sensible baseline to try first
  • Recognise each family's main weakness before it surprises you

You have now met six families of field surrogates. None is best for everything: the right one depends on how your data is stored, how much of it there is, how much the geometry changes and how large the meshes are. The table puts the questions side by side; the cards below recall how each family works and link back to its lesson.

FamilyData it takesGeometry changesMesh sizeTimeTraining data
Neural fieldPoints with coordinatesSmall changes (a few parameters)AnySteadyTens of runs of one shape family
U-NetRegular 2D grid, or a voxelised 3D oneYes, as a mask or SDFUp to ~10⁶ cells in 3DSteady or stepwiseHundreds of 2D cases
Fourier neural operatorRegular gridLittle; GINO for 3D shapesUp to ~10⁶ cellsSteady or stepwiseParametric PDE families on fixed domains
MeshGraphNetThe simulation meshYesUp to ~10⁶ nodesRolloutsTransient structures, crash, small CFD
TransolverPoints or mesh cellsYes10⁵ to 10⁶ points (more with Transolver++)Steady or stepwiseHundreds of 3D shapes
Latent transformerPoints, surface and volumeYes10⁶ to 10⁸ cellsSteady or latent rolloutHundreds to thousands of industrial runs

A sensible first baseline

  • A few scalars from a few parameters: no network at all. A Gaussian process or a polynomial, tested on held-out runs.
  • A field, one shape family, tens of runs: POD plus a GP on the coefficients, and a neural field. Both train in minutes.
  • Many shapes on a 2D grid: a U-Net. On a fixed domain with smooth fields, try an FNO next to it.
  • Many 3D shapes on unstructured meshes: Transolver or a latent transformer, from a framework such as PhysicsNeMo or the Neural-Solver-Library rather than written from scratch. Add a MeshGraphNet when the problem is transient or local.

Whatever you choose, keep the simple baseline in the comparison. If a large model does not clearly beat it on held-out designs, the large model is not yet earning its cost.

The families, one by one

pointα, ΛFourierfeatures48hidden48hidden48hiddenCpone surface point in, one pressure out

The simplest full-field surrogate, and the one this site runs. An MLP looks at one point at a time: its position and normal plus the design parameters go in, and the field value at that point comes out. Evaluate it at every point and you have the whole field.

⚙ In industry terms → Like a calibration curve per sensor location: give it a point on the car and the operating condition, and it returns the pressure there.

  • Fourier features turn the coordinates into sines and cosines at several frequencies, so the network can draw sharp suction peaks.
  • Three hidden layers of 48 units; 6,337 weights in total.
  • Points are independent, so evaluation is parallel and maps naturally onto GPUs.
Good for
One shape family with a few design parameters; any resolution; tiny and fast.
Watch out
It never sees its neighbours, so it knows the geometry only through coordinates and generalises poorly to new shapes.
Fourier features: Tancik et al., NeurIPS 2020

The lesson: A first field surrogate

Check yourself

Forty parametric variants of one wing, surface pressure only, 8,000 points each. What would you try first?

A neural field (or a GP on POD coefficients). The shape family is small and smooth; a large transformer would mostly overfit.

Five hundred different car shapes, 10 million cells each, surface and volume. What family?

A linear-cost transformer with a query decoder (Transolver-style or UPT/AB-UPT), trained on subsampled points.

A transient structural problem on an unstructured mesh, 50,000 nodes. What family?

A MeshGraphNet, rolled out in time and trained with input noise. The mesh is the natural graph and interactions are local.

Next unit · Scale and trustTraining on industrial meshes