// undine docs

Solver Architecture

Undine is designed both as a production-ready tool and as a platform for continued research and experimentation in fluid simulation techniques.

This chapter gathers the architectural material that matters most to technical users, developers, and researchers.

System Overview

The Undine system is intentionally layered so that the Blender-facing experience remains separate from the simulation core.

This separation keeps the user interface flexible while preserving the numerical integrity of the solver.

Solver architecture diagram

Undine is organized as a layered system that connects Blender scene setup to a dedicated simulation core.

graph TD
    Blender["Blender Scene"]

    Addon["Undine Blender Addon"]
    UIPanels["UI Panels"]
    SimConfig["Simulation Configuration"]
    SceneExport["Scene Export"]
    SimControl["Simulation Control"]

    Interface["Simulation Interface Layer"]
    ConfigFiles["Configuration Files"]
    CommandExec["Command Execution"]
    DataExchange["Data Exchange"]

    Core["Undine Simulation Core (C++)"]
    ParticleSystem["Particle System"]
    Neighbor["Neighbor Search"]
    Pipeline["Solver Pipeline"]
    Advection["Advection"]
    Boundary["Boundary Handling"]
    Viscosity["Viscosity"]
    Pressure["Pressure Solver"]
    Redist["Particle Redistribution"]
    Output["Output System"]
    Cache["Particle Cache"]
    MeshGen["Mesh Generation"]

    Blender --> Addon
    Addon --> UIPanels
    Addon --> SimConfig
    Addon --> SceneExport
    Addon --> SimControl
    Addon --> Interface
    Interface --> ConfigFiles
    Interface --> CommandExec
    Interface --> DataExchange
    Interface --> Core
    Core --> ParticleSystem
    Core --> Neighbor
    Core --> Pipeline
    Pipeline --> Advection
    Pipeline --> Boundary
    Pipeline --> Viscosity
    Pipeline --> Pressure
    Pipeline --> Redist
    Core --> Output
    Output --> Cache
    Output --> MeshGen
Layered system view
Blender Scene
|
+-- Undine Blender Addon
|   +-- UI Panels
|   +-- Simulation Configuration
|   +-- Scene Export
|   +-- Simulation Control
|
+-- Simulation Interface Layer
|   +-- Configuration Files
|   +-- Command Execution
|   +-- Data Exchange
|
+-- Undine Simulation Core (C++)
    +-- Particle System
    +-- Neighbor Search
    +-- Solver Pipeline
    |   +-- Advection
    |   +-- Boundary Handling
    |   +-- Viscosity
    |   +-- Pressure Solver
    |   +-- Particle Redistribution
    |
    +-- Output System
        +-- Particle Cache
        +-- Mesh Generation

Philosophy of the Solver

Fluid simulation systems are extremely sensitive to numerical consistency.

Even small mismatches between solver stages can produce visible artifacts, and those issues are often caused by subtle inconsistencies between multiple subsystems rather than by a single isolated algorithm.

For this reason, Undine approaches fluid simulation as a coherent numerical system rather than a collection of isolated features.

Architectural consequences

This philosophy influences the modular stage design, the strict separation between simulation logic and user interface, and the careful validation of numerical changes.

Consistency Across Execution Paths

In many fluid systems, CPU and GPU execution paths can behave slightly differently.

Perfect numerical equivalence is rarely possible in practice, but maintaining comparable solver behavior remains an important design objective because it improves predictability and reproducibility across systems and configurations.

Extensibility and Long-Term Evolution

Fluid simulation research continues to evolve rapidly. New numerical methods, acceleration techniques, and meshing algorithms are regularly developed by the simulation and computer graphics communities.

Because the simulation pipeline is modular, individual solver stages can be replaced or upgraded as new approaches become available.

This design allows Undine to grow over time while maintaining a stable foundation for existing workflows.

Orchestration layer

The orchestration layer sits between the solver core and the addon. It owns the frame loop, the substep loop, route resolution, retry coordination, and stage scheduling — the parts of the system that decide which kernel runs, in what order, on which backend, with which fallback.

Keeping orchestration separate from algorithms is what allows the retry chain (PCG → FP64 → MG → CPU) to exist without leaking into individual stages. Each stage just does its job; the orchestration layer decides whether to escalate.

Pipeline stages

  • Emission — particle creation from emitters, with temporal interpolation across substeps
  • Advection / Forces — gravity, drag, surface tension contribution
  • P2G — particle-to-grid transfer (APIC, MLS-MPM stress fold)
  • Pressure — projection (PCG, optional V-cycle multigrid, density correction)
  • Viscosity — when enabled (Simple / Implicit), physically motivated viscosity solve
  • G2P — grid-to-particle transfer with FLIP/PIC blend
  • Collisions — SDF lookup, normal damping, tangential friction, no-slip, contact refill
  • Surface tension — when enabled, on the free-surface band
  • Particle redistribution — reseeding, separation, mass-aware coverage
  • Streamflow publish — frame committed to the shared cache
  • Output write — disk persistence of expensive points and (optionally) mesh

Bricks subsystem

The bricks subsystem is the sparse-grid layer. It tracks active simulation regions as bricks with a halo wide enough for the pressure stencil, validates halo sufficiency / G2P coverage / extrapolation coverage, and decides authority transitions (dense ↔ velocity-pages) based on observed coverage and topology stability.

Authority demotions are logged with reasons (ParticleCore, ParticleHalo, TemporalEmission, ColliderBand, PressureBand, ExtrapolationBand, G2PSampling, PredictedMotion, RetainedTtl). When a sim is demoting often, the reason mask points at the cause.

GPU multigrid pressure

The PCG path optionally uses a V-cycle multigrid as preconditioner. The smoother is selectable: Jacobi (default, simple, cache-friendly) or symmetric red-black Gauss-Seidel (sym_rbgs) — single-sweep RBGS is intentionally not exposed because it breaks the SPD invariant PCG requires.

MG levels, V-cycles, smoother iterations, and a 'safe retry' policy with target-levels cap are tunable. The retry path uses weighted Jacobi at the coarse level when MG converges poorly on adversarial scenes.

FP32 reductions in the inner PCG loop are computed via warp-shuffle two-stage reductions (no shared-memory bank conflicts), with FP64 accumulation for determinism within a block.

Numerical safety net

Undine treats numerical failure as a predictable event, not an edge case. Three mechanisms protect long bakes:

FP32 tolerance floor

Below ~√N · ε_f32, the FP32 PCG residual is fighting roundoff, not the actual problem. Requested tolerances are clamped from below by a configurable safety factor (default 4.0). The floor is logged.

True-residual refresh

The CPU PCG path periodically recomputes b - A·x to reset the recurrence residual against drift. This kills the 'silent non-convergence' on long, ill-conditioned solves.

Retry chain

On breakdown (max iters, NaN, FP32 plateau): retry → FP64 → multigrid V-cycle → CPU. Each step is logged with code, iters, rel_res, and chosen backend.