For the full commented template, see:
# ==============================================================================
# PICurv Master Case Configuration Template
# ==============================================================================
#
# PURPOSE:
# This file defines the complete physical setup for a single simulation experiment.
# It describes the geometry, fluid properties, run duration, physical models,
# and boundary conditions. It is the "digital lab notebook" for your run.
#
# SECTIONS OVERVIEW:
# 0. title: Human-readable name; the prefix of every run ID.
# 1. run_control: How long the simulation runs and at what time resolution.
# 2. properties: The core physical numbers (density, viscosity, scales).
# 3. grid: The geometric domain, either from a file or generated.
# 4. models: Switches to turn on/off different physics modules (LES, particles, etc.).
# 5. boundary_conditions: What happens at the edges of the domain.
#
# YAML SYNTAX NOTE:
# - Indentation matters! Use spaces, not tabs.
# - For lists, ensure there is a space after the hyphen (e.g., "- item").
# - For more details: https://learnxinyminutes.com/docs/yaml/
#
# FILE PATHS:
# Inside a workspace (a directory made by `picurv init`), every relative path
# in this file resolves against the workspace root, not against this file.
# Absolute paths and paths that escape the workspace ("../") are refused.
# Bring outside data in with `picurv inputs import <kind> <source>`; kinds and
# destinations are grid -> inputs/grids, initial-condition ->
# inputs/initial_conditions, inlet-profile -> inputs/inlet_profiles,
# reference-field -> inputs/reference_fields. `--mode reference` records an
# external file without copying it; name the written `<name>.reference.yml`.
#
# ==============================================================================
# Optional. Run IDs are `<title>_<timestamp>`; without a title the file stem is
# used. `picurv init` sets it to the template name.
title: "master_template"
# ==============================================================================
# 1. SIMULATION CONTROL
# Defines the duration and temporal resolution of the experiment.
# ==============================================================================
run_control:
# The simulation step number to start from.
# 0 for a new run, or a specific step number for a restart.
start_step: 0
# Total number of timesteps to execute in this run.
# Total simulation time = total_steps * dt_physical.
total_steps: 2000
# Restart: use the --restart-from CLI flag to point at a previous run
# directory. See `picurv run --help` for details.
# The physical time increment for each step, in seconds [s].
dt_physical: 0.001
# ==============================================================================
# 2. PHYSICAL PROPERTIES
# Defines the non-dimensional scaling and material properties.
# ==============================================================================
properties:
# --- Scaling ---
# These reference values are used to non-dimensionalize the problem for the solver.
scaling:
length_ref: 0.05 # [m] Characteristic length of the domain (e.g., pipe diameter, chord length).
velocity_ref: 1.5 # [m/s] Characteristic velocity (e.g., inlet velocity, freestream velocity).
# --- Fluid ---
# Properties of the fluid being simulated.
fluid:
density: 1000.0 # [kg/m^3]
viscosity: 0.001 # [Pa.s or kg/(m.s)]
# --- Initial Conditions ---
# The state of the fluid at the start of the simulation (t=0).
# Values are in physical units [m/s].
initial_conditions:
# Built into the solver: zero | constant | streamwise_constant | poiseuille
# Python providers run by picurv before the solve (single block, staged grid):
# ic_gen | spectral_random_velocity | resampled_velocity | channel_spectral_velocity | duct_spectral_velocity
# Python providers need a grid picurv can read before launch: `file` or
# `grid_gen`, or `programmatic_c` for ic_gen only. Their payload is always
# written to <run.inputs>/initial_condition/; destination keys (output_file,
# summary_json, spectrum_csv) are refused. `picurv precompute` builds it
# without running the solver.
mode: generated
generator: constant
params:
u_physical: 0.0 # [m/s]
v_physical: 0.0
w_physical: 1.0
# --- Generator: streamwise_constant ---
# Sets every cell to the same scalar speed along the dominant flow axis.
# Infers flow_direction from the INLET face when one exists; otherwise,
# flow_direction is required.
# mode: generated
# generator: streamwise_constant
# params:
# velocity_physical: 1.0 # [m/s] Speed along the flow axis
# flow_direction: "+Zeta" # "+Xi" | "-Xi" | "+Eta" | "-Eta" | "+Zeta" | "-Zeta"
# # Optional when a single INLET face is present.
# --- Generator: poiseuille ---
# A product of 1-D parabolas in logical coordinates, one per wall-bounded axis:
# zero on WALL faces, peak_velocity_physical at the centre, and uniform along
# periodic axes. It is not the exact square-duct series solution; for that, use
# an inlet `prescribed_flow` profile from square_duct_poiseuille.
# flow_direction is required (or inferred from INLET face).
# mode: generated
# generator: poiseuille
# params:
# peak_velocity_physical: 1.5 # [m/s] Centerline (maximum) velocity
# flow_direction: "+Zeta" # Same options as streamwise_constant
# --- File IC: one PETSc .dat velocity field, staged for ReadFieldData. ---
# The payload is physical ([m/s] for Ucat, [m^3/s] per face for Ucont) and is divided
# by its reference scale when staged. A field saved by an earlier PICurv run is in that
# run's solver units: name the case that wrote it, or give its scales directly.
# mode: file
# field: Ucat # Ucat | Ucont
# source_file: inputs/initial_conditions/velocity.dat # via `picurv inputs import initial-condition`
# source_case: runs/<earlier_run>/config/case.yml # optional; or velocity_scale (+ length_scale for Ucont)
# --- External deterministic generator (ic_gen), materialized during run/precompute. ---
# Repository ic.gen supports file, grid_gen, and programmatic_c grids.
# For programmatic_c, scalar programmatic_settings are first materialized as the
# <run.inputs>/grid/grid.run bridge. Evaluates an INI [expression] section in physical
# units: x, y, z in [m], velocities in [m/s], Ucont fluxes in [m^3/s].
# mode: generated
# generator: ic_gen
# params:
# field: Ucat # Ucat | Ucont
# script: tools/custom_ic.py # Optional; defaults to generators/ic.gen
# config_file: config/initial_conditions/expression.cfg
# cli_args: [] # Optional raw tokens appended to the generator call
# --- Spectral random velocity: triply periodic box, all faces PERIODIC/geometric. ---
# Seeded Gaussian field shaped by a spectrum, optionally projected solenoidal and
# rescaled. The decaying_isotropic_turbulence example uses this block.
# mode: generated
# generator: spectral_random_velocity
# params:
# field: Ucat
# seed: 12345
# random: { distribution: gaussian, mean: [0.0, 0.0, 0.0] } # [m/s]
# spectrum: # white (default) | k4_exponential
# type: k4_exponential
# k0: 4.0 # [1/m] Energy-peak wavenumber
# k_cut: 20.0 # [1/m] Must not exceed the two-thirds resolution limit
# projection: { type: solenoidal, operator: picurv_discrete } # or { type: none }
# normalization: { type: component_rms, target: 1.0 } # [m/s]; or { type: none }
# remove_mean: true
# --- Import an external periodic velocity field (resampled_velocity). ---
# Brings a field from another code or resolution onto this grid: Fourier resampling,
# optional filter, projection to PICurv's discrete divergence-free space. Periodic
# boxes only; box_length must equal the grid's periods. Import the file first:
# picurv inputs import reference-field <file> --mode copy
# mode: generated
# generator: resampled_velocity
# params:
# source_file: inputs/reference_fields/field.bin
# format: raw # raw | npy | npz | hdf5 (hdf5 needs h5py)
# cells: [128, 128, 128] # source samples along x, y, z; required for raw
# box_length: [6.283185307179586, 6.283185307179586, 6.283185307179586] # [m]
# sample_offset: 0.0 # 0: samples on nodes from the origin; 0.5: cell centres
# layout:
# fastest_axis: x # x | z
# components: interleaved # interleaved (u,v,w per point) | separate
# dtype: float32 # raw only: float32 | float64
# byte_order: big # raw only: little | big | native
# header_bytes: 1536 # raw only
# filter: {type: none} # none | sharp {cutoff} | gaussian {width} | box {width}
# projection: picurv_discrete # picurv_discrete | continuum | none
# field: Ucont # Ucont (default, staged fluxes) | Ucat
# remove_mean: true
# --- Wall-bounded spectral seed: channel (one wall pair) or duct (two). ---
# A divergence-free perturbation (discrete curl of a seeded vector potential with a
# wall envelope) on a flux-normalized mean. Requires a single Cartesian block with
# uniform periodic axes: WALL/noslip pairs on wall_axes, PERIODIC elsewhere, and
# only the streamwise pair driven (constant_flux or initial_flux). Velocities are
# in physical units [m/s]. The turbulent_channel example uses this block.
# mode: generated
# generator: channel_spectral_velocity # or duct_spectral_velocity
# params:
# field: Ucat
# seed: 12345
# wall_axes: [j] # channel default [j]; duct default [i, j]
# streamwise_axis: k # Must be periodic
# bulk_velocity: 1.0 # [m/s] Mean of the startup profile
# perturbation_rms: 0.1 # [m/s]
# wall_modes: 3 # Wall-normal modes; wall_modes + 1 <= cells/2 per wall axis
# spectrum: { type: k4_exponential, k0: 4.0, k_cut: 8.0 } # wavenumbers in [1/m]
# initial_spectra: # Optional plane/line spectra of the seed, periodic axes only
# - { task: plane_spectrum, axes: [i, k], fixed_indices: { j: 8 }, subtract_mean: sample }
# ==============================================================================
# 3. GRID DEFINITION
# Defines the computational mesh for the simulation.
# ==============================================================================
grid:
# --- Mode Selection ---
# Determines how the grid is provided to the solver.
# Options: 'file', 'programmatic_c', or 'grid_gen'.
mode: programmatic_c
# --- Optional Global DMDA Layout (applies to all grid modes) ---
# Edit or remove these for parallel runs. The product must equal the total
# MPI process count (-n) when all three are set.
da_processors_x: 2
da_processors_y: 2
da_processors_z: 4
# --- Option A: Grid from File ---
# Use this if you have a pre-generated grid file.
# The script validates and non-dimensionalizes coordinates using 'length_ref'.
# source_file: "inputs/grids/my_premade_grid.picgrid" # via `picurv inputs import grid`
# --- Option B: Grid via Python Grid Generator (generators/grid.gen) ---
# Use this for complex curvilinear meshes generated before solver launch.
# generator:
# config_file: "config/grids/plane_channel_retau180.cfg" # Required today. Workspace-relative.
# grid_type: "box" # Optional override: box | sweep
# # `box` is a Cartesian block whose walls may be shaped; `sweep` carries a
# # cross-section along a centreline. See 48_Grid_Generator_Guide.
# cli_args: # Optional raw CLI token list
# - "--ncells-i"
# - "96"
# - "--ncells-j"
# - "96"
# Destinations are not configurable: PICurv writes the grid, its .vts preview,
# and its .info quality report into the run's own asset store.
# --- Option C: Programmatic Grid Generation in C ---
# Use this to have the C solver generate a structured Cartesian grid.
# `im/jm/km` are cell counts in YAML; picurv converts them to node counts
# before emitting `-im/-jm/-km` for the C runtime.
programmatic_settings:
# --- Cell Counts ---
im: 64 # [Cells in i-direction]
jm: 32 # [Cells in j-direction]
km: 128 # [Cells in k-direction]
# --- Domain Bounds (in physical units [m]) ---
xMins: 0.0 # [Min x-coordinate]
xMaxs: 1.0 # [Max x-coordinate]
yMins: 0.0
yMaxs: 0.5
zMins: 0.0
zMaxs: 2.0
# --- Grid Stretching Ratios ---
# 1.0 = uniform. >1.0 = stretching towards max coordinate.
rxs: 1.0
rys: 1.05
rzs: 1.0
# --- Periodic Seam Formula Flag ---
# This block is always built as a stretched Cartesian box; nothing here can produce
# cylindrical/O-grid coordinates. `cgrids` only selects which formula the C metric
# routines use to reconstruct ghost cells at a periodic seam: 0 (default) assumes a
# translationally periodic box, 1 assumes a coincident-node wrap-around (annulus).
# Setting 1 on this Cartesian geometry corrupts the periodic metric. Leave at 0.
# -> -cgrids
cgrids: 0
# ==============================================================================
# 4. MODEL SELECTION
# User-friendly switches to enable different physical models and features.
# If a section or key is omitted, the C-code's default will be used.
# ==============================================================================
models:
# --- Core Physics Modules ---
physics:
dimensionality: "3D" # Options: "3D" (default), "2D" (sets -TwoD 1: the i velocity
# component is held fixed; the grid is unchanged)
particles:
count: 0 # [Integer] -> -numParticles
init_mode: "Surface" # [string] -> -pinit. Options: "Surface", "Volume", "PointSource", "SurfaceEdges"
restart_mode: "init" # [string] -> -particle_restart_mode. Options: "init", "load"
random_seed: 12345 # [Integer, optional] -> -particle_random_seed. 0..2147483647; seeds
# initial placement and Brownian draws (default 12345)
point_source: # Required only when init_mode = "PointSource" (maps to -psrc_x/-psrc_y/-psrc_z)
x: 0.5 # [m] Physical position, divided by length_ref like the grid bounds.
y: 0.25 # This one is the centre of the domain defined under grid.
z: 1.0
# fields: # [optional] -> -particle_fields_*. Initial values of particle-carried
# # fields (today Psi), applied once positions are final. Each value is
# # a number, an expression in x y z (physical), xn yn zn (0..1 over the
# # domain), pid, t, uniform(), normal(), or regions over a background:
# Psi:
# background: 0
# regions: # later regions paint over earlier ones
# - {shape: half_space, axis: x, at: 0.5, side: above, value: 1}
# # shapes: half_space, slab, box, ball, cylinder; optional
# # edge: {profile: linear | smoothstep | cosine, width: <m>}
turbulence:
# Preferred structured LES form. Legacy shorthand is still accepted:
# les: false -> -les 0
# les: true -> -les 1 (constant Smagorinsky)
# les: 1 -> constant Smagorinsky
# les: 2 -> dynamic Smagorinsky
# All four models are supported; see docs/pages/07_Case_Reference.md for each
# model's evidence and limits.
# Each model's own parameters are refused under the others.
les:
# [Boolean] -> -les 0 when false
enabled: false
# Which subgrid closure to evaluate. Only needed when enabled: true.
# constant_smagorinsky -> a prescribed coefficient; no coefficient field is
# allocated, checkpointed, or synchronized.
# dynamic_smagorinsky -> the Germano-Lilly procedure measures the
# coefficient from the resolved field each update.
# vreman -> resolved per grid direction from the cell's own
# edges; vanishes in pure shear; takes no filter_width.
# wale -> vanishes in pure shear and falls correctly toward a
# wall; one scalar width, so pair with scotti.
# [String] -> -les
# model: "constant_smagorinsky"
# The fixed Smagorinsky constant. constant_smagorinsky only.
# [Number] -> -les_constant_cs
constant_cs: 0.03
# Model constant c. vreman only. Vreman (2004): 2.5 Cs^2 = 0.07 for Cs = 0.17.
# [Number] -> -les_vreman_coefficient
# vreman_coefficient: 0.07
# Model constant C_w. wale only. Nicoud & Ducros (1999) calibrate 0.55-0.60.
# [Number] -> -les_wale_coefficient
# wale_coefficient: 0.5
# Recompute the dynamic coefficient every N steps. dynamic_smagorinsky only;
# the constant model has nothing to recompute.
# [Integer] -> -les_dynamic_frequency
dynamic_frequency: 1
# How each cell's grid filter width Delta is derived.
# cube_root_volume -> (cell volume)^(1/3); exact for a cube and increasingly
# optimistic as the cell is stretched.
# geometric_mean -> geometric mean of the cell's extents along its own grid
# directions; equals cube_root_volume on orthogonal cells.
# max_edge -> the longest extent; for wall-modelled or hybrid runs, not
# a wall-resolved mesh, where it swamps the wall layer.
# scotti -> cube root corrected for the cell's aspect ratios; the
# choice for moderately stretched wall-resolved cells.
# [String] -> -les_filter_width
filter_width: "cube_root_volume"
# The explicit second filter the dynamic procedure compares against the grid
# filter. A bare string is accepted as shorthand for naming only the kernel.
test_filter:
# volume_weighted_box -> general curvilinear grids.
# simpson_ik -> more accurate, but assumes xi and zeta are
# homogeneous; rejected unless both are PERIODIC.
# [String] -> -les_test_filter_kernel
kernel: "volume_weighted_box"
# Test-to-grid filter width ratio in each direction the kernel filters. The box
# filters all three, so alpha is its square; simpson_ik filters only xi and
# zeta, so alpha is ratio^(4/3). Must exceed 1: a test filter no wider than the
# grid filter leaves nothing to measure.
# [Number] -> -les_test_filter_width_ratio
width_ratio: 2.0
# Lilly's least-squares closure assumes the coefficient is constant across the
# averaging set, so the set should span directions the flow is homogeneous in.
averaging:
# local -> pointwise; the only choice valid on arbitrary geometry.
# homogeneous -> average over the directions named below, or over the
# periodic axes when none are named. A triply periodic box
# gives one coefficient; a channel gives a wall-normal profile.
# global -> average over the whole domain regardless of boundaries.
# [String] -> -les_averaging_mode
mode: "local"
# Explicit homogeneous directions, overriding the periodic default.
# homogeneous mode only.
# [List] -> -les_averaging_directions
# directions: ["i", "k"]
clipping:
# clamp -> restrict to [0, max_cs^2].
# clip_negative -> discard negatives, impose no ceiling.
# none -> keep the signed coefficient, so backscatter survives.
# Discarding the negative tail removes backscatter and biases the mean
# dissipation upward; the floor below is the safer way to bound the model.
# [String] -> -les_clip_mode
mode: "clamp"
# Ceiling on Cs, not on the stored coefficient. clamp mode only; naming it
# under another mode is an error, since no ceiling would be in force.
# [Number] -> -les_clip_max_cs
max_cs: 0.3
# Enforce nu + nu_t >= ratio * nu. Bounds the quantity that actually has to
# stay positive for the momentum operator to remain well posed.
# [Number] -> -les_min_viscosity_ratio
min_viscosity_ratio: 0.0
# The Clark gradient (tensor-diffusivity) term, added to the viscous flux.
# Combined with a Smagorinsky closure this is the mixed model. Available under
# both momentum solver families; the Newton-Krylov preconditioner does not model
# it, so watch Krylov counts if you enable it.
gradient_model:
# [Boolean] -> -les_gradient_model
enabled: false
# Per-step coefficient statistics appended to output/analysis/metrics/les_coefficient.csv:
# the effective Cs, its spread, eddy-viscosity levels, the modelled subgrid
# energy, and the fractions of the domain backscattering or limited before
# clipping. Those last two describe a state no stored field preserves.
diagnostics:
# [Boolean] -> -les_diagnostics
enabled: false
# Steps between rows.
# [Integer] -> -les_diagnostics_cadence
cadence: 1
# Yoshizawa constant C_I for the reported subgrid kinetic energy,
# k_sgs = 2 C_I Delta^2 |S|^2. Affects the k_sgs_mean column only, never the
# eddy viscosity or the momentum equations. Compare it against the resolved
# turbulent kinetic energy from monitor.yml -> field_statistics to judge how
# much of the energy the grid resolves.
# [Number] -> -les_yoshizawa_ci
yoshizawa_ci: 0.09
# There is no RANS selector: the closure is planned, not implemented, and a
# `rans:` block is refused at validation. Use `les` above, or run laminar.
# Wall functions are independent of the LES closure and apply on WALL faces. The
# correction is applied inside the momentum solve's boundary pass and again
# before the LES strain rates are formed, so the closure and the momentum
# equation agree about the near-wall velocity.
wall_function:
# [Boolean] -> -wallfunction 0 when false
enabled: false
# Which wall law to apply. Only needed when enabled: true.
# log_law -> logarithmic profile with roughness; iterative friction velocity
# werner -> Werner-Wengle power law; explicit, no roughness, valid into the
# viscous sublayer, and free of the inner iteration that a
# matrix-free Jacobian amplifies
# cabot -> integrated mixing-length eddy viscosity; its non-equilibrium
# form takes the wall-parallel gradient of the resolved pressure.
# Experimental: on the Re_tau ~ 1000 channel that term put u_tau
# 22% low, so prefer werner or log_law for results.
# The run stops if the mean first-cell y+ stays outside the law's range
# (log_law 30-300, werner <= 300, cabot <= 1000) for 10 consecutive steps.
# [String] -> -wallfunction
# model: "log_law"
# Wall roughness height. log_law only: validation rejects this key for werner
# and cabot, so delete it when selecting either.
# [Number] -> -wall_roughness
roughness_height: 1.0e-16 # [m] Physical length, divided by length_ref.
# ==============================================================================
# 5. BOUNDARY CONDITIONS
# Defines the behavior at each of the 6 faces of the computational domain.
# ==============================================================================
# --- Provide a list of 6 face definitions, one per face. ---
# --- Geometric periodicity: set both opposite faces to PERIODIC/geometric.
# Periodic axes are derived from these BC pairs. The paired grid surfaces
# must match pointwise under one nonzero constant Cartesian translation,
# and the axis must contain at least four physical nodes. Periodic particle
# wrapping is not implemented. See examples/periodic_test/. ---
boundary_conditions:
- face: "-Xi"
type: WALL
handler: noslip
- face: "+Xi"
type: WALL
handler: noslip
- face: "-Eta"
type: INLET
handler: constant_velocity
params:
vx: 1.5 # Physical velocity [m/s]
vy: 0.0
vz: 0.0
# Alternative inlet profile from a precomputed PICSLICE file:
# handler: prescribed_flow
# params:
# source:
# type: file
# path: inputs/inlet_profiles/inlet.picslice # via `picurv inputs import inlet-profile`
#
# Alternative generated analytical square-duct Poiseuille profile:
# handler: prescribed_flow
# params:
# source:
# type: generated
# generator: square_duct_poiseuille
# script: tools/custom_profile.py # Optional; defaults to generators/profile.gen
# params:
# bulk_velocity: 1.5
# n_terms: 101
#
# Alternative profile sliced from an old Cartesian Ucat field. The target grid
# must be file or grid_gen. Import the old run's files first, e.g.
# picurv inputs import reference-field <old_run>/output/checkpoints/<step>/eulerian/block_0000/Ucat.dat --mode reference
# picurv inputs import grid <old_run>/inputs/grid/grid.run --name old_grid.run
# Generated profiles are written under <run.inputs>/inlet_profiles/; output_file is refused.
# handler: prescribed_flow
# params:
# source:
# type: field_slice
# script: tools/custom_profile.py # Optional; defaults to generators/profile.gen
# field_file: inputs/reference_fields/Ucat.dat.reference.yml
# grid_file: inputs/grids/old_grid.run
# source_case: config/old_case.yml # A copy of the old case.yml, or velocity_scale: <old U_ref>
# source_block: 0 # Optional
# slice:
# face: "+Eta"
# orientation: opposite
- face: "+Eta"
type: OUTLET
handler: conservation
- face: "-Zeta"
type: WALL
handler: noslip
- face: "+Zeta"
type: WALL
handler: noslip
# --- All Supported BC Handlers (reference) ---
#
# WALL faces:
# handler: noslip No-slip (zero velocity) wall.
#
# INLET faces:
# handler: constant_velocity Uniform Cartesian velocity.
# params: { vx: 1.5, vy: 0.0, vz: 0.0 }
#
# handler: parabolic Fully-developed parabolic (Poiseuille) profile.
# params: { v_max: 1.5 } v_max = centerline (peak) velocity [m/s].
#
# handler: prescribed_flow Profile from a file, generator, or field slice.
# params:
# source:
# type: file
# path: inputs/inlet_profiles/inlet.picslice
#
# # OR generate analytically:
# # source:
# # type: generated
# # generator: square_duct_poiseuille
# # params:
# # bulk_velocity: 1.5
# # n_terms: 101
#
# # OR slice from an existing Ucat field:
# # source:
# # type: field_slice
# # field_file: inputs/reference_fields/Ucat.dat.reference.yml
# # grid_file: inputs/grids/old_grid.run
# # source_case: config/old_case.yml
# # slice:
# # face: "+Eta"
# # orientation: opposite
#
# OUTLET faces:
# handler: conservation Mass-conservation outflow correction.
#
# PERIODIC faces (must appear as matching opposite pairs):
# handler: geometric Pure geometric periodicity (no driving force).
# type: PERIODIC
# handler: constant_flux Drives a constant volume flux through the periodic pair.
# type: PERIODIC
# params:
# target_flux: 1.0 Target volumetric flux in PHYSICAL units [m^3/s]; picurv
# divides it by (U_ref * L_ref^2) before the C runtime.
# enforce_seam_flux: false [true/false] Optional, default false. Adds a local
# correction to the face fluxes of the periodic SEAM PLANE
# so the flux through that one plane matches the target.
# The body force already holds the bulk flux; this only
# addresses the seam. Leave false unless Bcs.Uch shows a
# persistently large seam mismatch - enabling it can
# reintroduce divergence and injects single-plane noise.
# `apply_trim` is accepted as a deprecated alias.
# handler: initial_flux Like constant_flux, but holds the flux of the initial
# type: PERIODIC condition instead of a configured target. Takes no
# params: {} target_flux; enforce_seam_flux is optional.
# ==============================================================================
# 6. ADVANCED PASSTHROUGH (OPTIONAL)
# ------------------------------------------------------------------------------
# Use this for C flags not yet exposed in the structured schema.
# Keys must be full C/PETSc-style flags (including leading '-').
# ==============================================================================
# solver_parameters:
# -some_new_flag: 123
case.yml defines physical setup, grid source, domain topology, and boundary conditions. It is intentionally modular: the same case.yml can be paired with different solver.yml, monitor.yml, and post.yml profiles when the combination remains contract-compatible.
Alternative IC forms:
Key mappings:
scaling.length_ref -> -scaling_L_refscaling.velocity_ref -> -scaling_U_reffluid.density and fluid.viscosity are used by picurv to compute Reynolds number -> -rengenerator: zero|constant|poiseuille|streamwise_constant -> the corresponding built-in -finit modemode: file and Python IC providers (ic_gen, spectral_random_velocity, resampled_velocity, channel_spectral_velocity, duct_spectral_velocity) -> -finit 4, -ic_field, and staged -ic_dirparams.u_physical/v_physical/w_physical -> -ucont_x/-ucont_y/-ucont_zparams.velocity_physical and params.peak_velocity_physical -> -ic_velocity_physicalflow_direction -> -flow_direction <int> (+Xi=0,-Xi=1,+Eta=2,-Eta=3,+Zeta=4,-Zeta=5)For the scaling model and conversion logic, see Units and Non-Dimensionalization. For detailed startup behavior of field initialization modes, see Initial Condition Modes.
Practical contract notes:
initial_conditions.mode is generated or file.zero, constant, streamwise_constant, and poiseuille.generator: ic_gen defaults to generators/ic.gen; optional params.script selects a compatible override.Ucat or Ucont vector and currently require a single-block case.flow_direction is required for curvilinear Constant and Poiseuille when no INLET face exists.eulerian_field_source and restart selection supersede initial_conditions.Mappings:
dt_physical -> -dt (non-dimensionalized)start_step -> -start_steptotal_steps -> -totalstepsRestart is handled via CLI flags rather than case.yml keys:
--restart-from <previous_run_dir> -> picurv resolves the prior run's actual restart source directory and emits -restart_dir <absolute_previous_source>--continue -> shorthand for resuming from the most recent run of the same caseSupported modes:
programmatic_cfilegrid_genMode compatibility note:
solve and load workflows, all three grid modes are supported.solver.yml -> operation_mode.eulerian_field_source: analytical, TGV3D requires grid.mode: programmatic_c.ZERO_FLOW and UNIFORM_FLOW support grid.mode: programmatic_c and grid.mode: file.Optional global DMDA layout hints apply to all grid modes:
These are scalar global values, not per-block vectors. Legacy placement under grid.programmatic_settings.da_processors_* is still accepted for compatibility, but the shared top-level grid.da_processors_* form is preferred.
programmatic_settings supports per-block lists for geometry arrays:
im/jm/kmxMins/xMaxs, yMins/yMaxs, zMins/zMaxsrxs/rys/rzscgrids — per-block integer, forwarded to -cgrids, default 0. It does not change the geometry programmatic_c builds: src/grid.c always produces a stretched Cartesian block regardless of its value. The only consumer is Metric.c's periodic ghost reconstruction, where cgrids: 1 swaps a translational seam correction for a coincident-node one appropriate to a true wrap-around (O-grid/annulus) topology. Setting it to 1 on the Cartesian geometry this mode actually builds corrupts the metric at a periodic seam rather than enabling curvilinear geometry; leave it at 0.Dimension contract:
im/jm/km in YAML are cell counts.picurv converts them to node counts before emitting -im/-jm/-km for the C runtime.xMins/xMaxs/yMins/yMaxs/zMins/zMaxs are physical units, exactly like a file-mode .picgrid. The C runtime divides each bound by properties.scaling.length_ref immediately after parsing them, so the domain the solver evolves is non-dimensional regardless of which grid.mode produced it. See 4. Field Index.Important constraint:
grid.da_processors_x/y/z are scalar integers only (global DMDA layout). Per-block processor decomposition is not implemented.picurv validates existence and prepares normalized grid data for C-side ingestion. grid.legacy_conversion was removed; a .picgrid staged this way must already be in canonical format.
This runs generators/grid.gen before solver launch and stages generated grid artifacts into the run config. grid.generator.config_file is required today; picurv does not synthesize a temporary grid config. grid.gen accepts cell-count inputs (ncells_* / --ncells-*) and writes node counts into the generated .picgrid header.
For direct grid.gen usage, generator types, and config-file structure, see Grid Generator Guide: generators/grid.gen.
| Value | Maps to |
|---|---|
file | file |
grid_gen | grid_gen |
programmatic_c | programmatic_c |
Identity. grid.mode: file with grid.source_file naming a .picgrid file.
What it does. Reads an externally produced curvilinear mesh. picurv validates that the file exists and stages it as <run.inputs>/grid/grid.run, so the run carries the mesh it was computed with rather than a path that may later change.
When to choose it. When the geometry came from grid.gen, from a mesher, or from a previous study, and you want the exact same mesh across runs. Prefer it over grid_gen once a mesh is settled: regenerating on every launch is a way to silently change geometry.
Parameters it owns. grid.source_file -> the staged .picgrid path. Global DMDA hints grid.da_processors_x/y/z apply here as to every mode.
Interactions. Mutually exclusive with programmatic_c and grid_gen. Compatible with the ZERO_FLOW and UNIFORM_FLOW analytical sources; TGV3D is not, because it needs the programmatic Cartesian box.
Diagnostics. The startup banner reports the resolved grid source. A missing or unreadable file is a fatal picurv validate error naming the path, not a runtime failure.
Evidence. Production exercised - examples/search_robustness runs from a staged .picgrid. It carries the only grid file the examples still ship: bent_channel moved to grid_gen once the generator could express its geometry, which removed a duplicate of the same 2.9 MB file. Analytically verified - curvilinear-gcl-2026-09-18: the face-area metrics of a curvilinear file grid close to round-off in every cell; eulerian-source-domain-modes-2026-09-21: a curvilinear pipe loaded as a file reproduced the grid, velocity and pressure of the same run from grid_gen exactly after ten steps; grid-generator-closure-2026-09-21: every generator feature, loaded as a file grid, closes a uniform flow to round-off.
Limitations. picurv does not inspect the mesh beyond existence and header validity, so a geometrically wrong but well-formed file is accepted here and only shows up in metric or Jacobian diagnostics. The solver itself refuses a uniformly left-handed file, whose logical axes would need renumbering (p48_cap_xform_reverse).
Identity. grid.mode: programmatic_c with a grid.programmatic_settings block.
What it does. Builds a structured block directly in the C runtime from cell counts, extents, and stretching ratios. No mesh file exists or is read.
When to choose it. For Cartesian boxes - channels, ducts, isotropic boxes - and for anything where the geometry is a handful of numbers you want in the case file rather than in a binary alongside it. It is also the only mode TGV3D accepts.
Parameters it owns. grid.programmatic_settings.im/jm/km (cell counts, converted to node counts before -im/-jm/-km), xMins/xMaxs, yMins/yMaxs, zMins/zMaxs, rxs/rys/rzs, and cgrids -> -cgrids - see 3.1 mode: programmatic_c for why this is not a real geometry choice.
Interactions. Mutually exclusive with file and grid_gen. Required by the TGV3D analytical source. Per-block lists are supported for the geometry arrays, but grid.da_processors_* stay scalar: per-block processor decomposition is not implemented.
Diagnostics. The startup banner reports the resolved block extents and counts. A count/extent mismatch across per-block lists is a validation error naming the offending key.
Evidence. Production exercised - examples/flat_channel; integration verified - make unit-grid. Analytically verified - duct-inlet-handlers-2026-09-21: a programmatic square duct develops to the Poiseuille series at second order; poisson-options-2026-09-21 and initial-conditions-2026-09-21 run on it.
Limitations. Restricted to a rectangular Cartesian block - cgrids does not add curvilinear geometry (3.1 mode: programmatic_c). Anything bent, branched, or externally meshed needs file or grid_gen.
Identity. grid.mode: grid_gen with a grid.generator block naming a config file and a grid_type.
What it does. Runs generators/grid.gen before the solver launches and stages the generated .picgrid as <run.inputs>/grid/grid.run. The generated mesh, not the generator invocation, is what the solver reads.
When to choose it. While a geometry is still being iterated - sweeping cell counts or bend angles across a study, where regenerating per case is the point. Once the mesh is settled, switch to file so the geometry stops depending on the generator's behaviour.
Parameters it owns. grid.generator.config_file (required; picurv does not synthesize one), grid.generator.grid_type -> 7.1 Grid Generator Geometry Entries, and grid.generator.cli_args for per-run overrides such as --ncells-i.
Interactions. Mutually exclusive with file and programmatic_c. The generator accepts cell counts (ncells_*) and writes node counts into the .picgrid header, so the count convention matches programmatic_c at the YAML boundary.
Diagnostics. Generator stdout is captured into the run's scheduler log, and the staged grid appears as <run.inputs>/grid/grid.run. A generator failure aborts before the solver launches.
Evidence. Production exercised - examples/periodic_test cases stage grids this way. Analytically verified - duct-poiseuille-picard-2026-09-18 on a generated box and pipe-poiseuille-curvilinear-2026-09-18 on a generated swept circle, each at second order.
Limitations. Adds a build step to every launch, and the run's geometry depends on the generator's current behaviour rather than on a fixed artefact. config_file is mandatory today.
Common mappings:
PERIODIC boundary conditions before DMDA creation; models.domain does not accept periodic flagsphysics.dimensionality: "2D" -> -TwoD 1physics.turbulence.les.enabled/model -> -les (0 none, 1 constant Smagorinsky, 2 dynamic Smagorinsky, 3 Vreman, 4 WALE)physics.turbulence.les.constant_cs -> -les_constant_csphysics.turbulence.les.vreman_coefficient -> -les_vreman_coefficientphysics.turbulence.les.wale_coefficient -> -les_wale_coefficientphysics.turbulence.les.dynamic_frequency -> -les_dynamic_frequencyphysics.turbulence.les.filter_width -> -les_filter_widthphysics.turbulence.les.test_filter.kernel/width_ratio -> -les_test_filter_kernel, -les_test_filter_width_ratiophysics.turbulence.les.averaging.mode/directions -> -les_averaging_mode, -les_averaging_directionsphysics.turbulence.les.clipping.mode/max_cs/min_viscosity_ratio -> -les_clip_mode, -les_clip_max_cs, -les_min_viscosity_ratiophysics.turbulence.les.gradient_model.enabled -> -les_gradient_modelphysics.turbulence.les.diagnostics.enabled/cadence/yoshizawa_ci -> -les_diagnostics, -les_diagnostics_cadence, -les_yoshizawa_ciphysics.turbulence.wall_function.enabled/model -> -wallfunction (1 log law, 2 Werner-Wengle, 3 Cabot)physics.turbulence.wall_function.roughness_height -> -wall_roughness (log_law only); a physical length, divided by length_refphysics.particles.count -> -numParticlesphysics.particles.init_mode -> -pinit (Surface, Volume, PointSource, SurfaceEdges)physics.particles.restart_mode -> -particle_restart_modephysics.particles.random_seed -> -particle_random_seed (integer 0..2147483647, default 12345)physics.particles.fields -> -particle_fields_* (optional initial values of particle-carried fields, today Psi: a number, an expression, or regions over a background; see 1.1 Configured Particle Values (fields))-psrc_x/-psrc_y/-psrc_z; a physical position, divided by length_refLegacy turbulence shorthand remains valid:
les: false -> -les 0les: true or les: 1 -> constant Smagorinsky (-les 1)les: 2 -> dynamic Smagorinsky (-les 2)wall_function is a sibling of les because a wall model is configured independently of the closure it pairs with; the pairing rules are listed with the wall laws in 6. Test-Filter and Wall-Function Entries. There is no RANS closure: models.physics.turbulence.rans is refused at validation, and the charter for a future one is at 6a. RANS Closures.
Restart note:
run_control.start_step > 0, particles are enabled, and restart_mode is omitted, picurv warns that C will default to load.For mode-specific particle behavior and restart flow, see Particle Initialization and Restart Guide.
| Value | Maps to |
|---|---|
2D | 2D |
3D | 3D |
models.physics.dimensionality must be exactly 3D or 2D; any other spelling is refused at validation.
Identity. models.physics.dimensionality: "3D", the default. No -TwoD flag is emitted, so the runtime value stays 0.
What it does. Evolves all three contravariant velocity components.
When to choose it. Every case that is not deliberately planar.
Parameters it owns. None.
Interactions. Required by generated initial conditions that build a fresh three-dimensional field, such as spectral_random_velocity.
Diagnostics. None of its own: the generated control file carries no -TwoD line.
Evidence. Production exercised - every shipped example, including examples/flat_channel, runs in 3D. Analytically verified - every solve measurement, for instance tgv2d-picard-order-2026-09-18, runs in 3D.
Limitations. None of its own.
Identity. models.physics.dimensionality: "2D" -> -TwoD 1.
What it does. Zeroes the i-direction row of the momentum right-hand side in every cell, so the i contravariant velocity component never moves from its initial value. Nothing else changes: the grid, the pressure solve, and the particles stay three-dimensional. It is a 3D solve with one momentum component held fixed, not a two-dimensional discretization.
When to choose it. A flow that is planar in the j-k plane, on a grid thin in i, with the i velocity component initialized to zero.
Parameters it owns. None.
Interactions. Refused by the Newton-Krylov solver and by generated initial conditions that need a fresh 3D field. Only the i direction can be held fixed from YAML; the runtime also accepts -TwoD 2 (j) and -TwoD 3 (k), reachable only through a PETSc passthrough.
Diagnostics. The generated control file carries -TwoD 1; there is no startup banner line for it.
Evidence. Unit verified - the active-row mask cases in tests/c/test_solver_kernels.c, run by make unit-solver. Analytically verified - eulerian-source-domain-modes-2026-09-21: a Taylor-Green vortex in the j-k plane with a frozen i velocity ran 20 steps in 2D and in 3D; the i component moved by at most 3.1e-16 and the j and k components matched the 3D run to 2.8e-15.
Limitations. Nothing checks that the i component starts at zero. The pressure projection still acts on every face, so the i component stays frozen only while the pressure field does not vary along i; a flow that develops an i pressure gradient breaks the planar assumption without warning.
The subgrid model is chosen by models.physics.turbulence.les.model.
| Value | Maps to | Status |
|---|---|---|
constant | 1 | accepted spelling of constant_smagorinsky |
constant_smagorinsky | 1 | supported |
disabled | 0 | accepted spelling of none |
dynamic | 2 | accepted spelling of dynamic_smagorinsky |
dynamic_smagorinsky | 2 | supported |
no_les | 0 | accepted spelling of none |
none | 0 | supported |
off | 0 | accepted spelling of none |
smagorinsky | 1 | accepted spelling of constant_smagorinsky |
vreman | 3 | supported |
wale | 4 | supported |
Identity. les.enabled: false, or model: none -> -les 0 -> NO_LES_MODEL. Accepted spellings: off, disabled, no_les. The legacy shorthand les: false is equivalent.
What it does. Disables subgrid modelling entirely. The momentum equations are solved with molecular viscosity alone.
When to choose it. Laminar cases, and any DNS where the grid resolves the dissipative scales. Also the correct choice while verifying something else: a subgrid model is one more thing to be wrong when you are chasing a discrepancy.
Parameters it owns. None. Setting constant_cs or dynamic-model controls alongside none has no effect.
Interactions. Wall functions are configured separately and are not implied by disabling LES.
Diagnostics. The startup banner reports the resolved turbulence model. With no model active, no eddy-viscosity field is computed or logged.
Evidence. Production exercised - examples/flat_channel and examples/bent_channel both run with no subgrid model.
Limitations. None; this is the absence of a model rather than a model.
Identity. model: constant_smagorinsky -> -les 1 -> CONSTANT_SMAGORINSKY. Accepted spellings: constant, smagorinsky. The legacy shorthand les: true and les: 1 both select this model.
What it does. Applies a Smagorinsky eddy viscosity built from a fixed coefficient and the local strain-rate magnitude, added to the molecular viscosity: nu_t = Cs^2 Delta^2 |S|.
When to choose it. When you want subgrid dissipation, know a coefficient appropriate to your flow, and would rather not pay for the dynamic procedure. It is also the right choice on a geometry with no homogeneous direction, where a local dynamic coefficient is at its noisiest.
Parameters it owns. constant_cs -> -les_constant_cs, the fixed coefficient, and filter_width -> -les_filter_width, which sets how Delta is derived per cell.
constant_cs defaults to 0.03, which is a wall-bounded value. Isotropic turbulence conventionally uses Lilly's 0.16-0.17, but only where the grid cutoff sits in an inertial range; at low Re_lambda a smaller value near 0.1 is more appropriate. The default is not a universal choice - set it deliberately for your flow.Interactions. dynamic_frequency and the test-filter, averaging, and clipping controls belong to the dynamic procedure, and the other models' coefficients to those models; all are rejected here rather than silently ignored.
Storage. This model allocates no coefficient field. The coefficient is a number from the configuration, so nu_t is built from it directly; nothing is synchronized, checkpointed, or written to disk on its behalf.
Diagnostics. The startup banner reports the resolved turbulence model. With diagnostics.enabled set, <run.analysis.metrics>/les_coefficient.csv records the eddy-viscosity levels and the modelled subgrid energy each step. The eddy-viscosity field is available for output.
Evidence. Implemented, with unit coverage: tests/c/test_les.c case constant-model-needs-no-coefficient-field runs the model with no coefficient field allocated and checks the resulting nu_t against the closed form. Benchmark characterized - hom02-les-execution-2026-10-09: on AGARD HOM02 decaying isotropic turbulence started from Wray's 512^3 DNS field, the model at constant_cs: 0.17 ran to the last DNS spectrum at 64^3, 32^3 and 64 x 64 x 32, and at 64^3 brought the cutoff-band energy from the unmodelled pile-up (mean log10 error +0.72 against the DNS) to +0.11.
Limitations. The coefficient is constant in space and time, so it cannot adapt near walls and does not vanish in laminar regions the way a dynamic procedure does. On HOM02 at 64^3 (hom02-les-accuracy-2026-10-09, not met) the resolved energy runs up to 26% above the DNS during the first 1.5 time units and ends 14% below it; the low-band spectrum is within 0.093 RMS in log10.
Identity. model: dynamic_smagorinsky -> -les 2 -> DYNAMIC_SMAGORINSKY. Accepted spelling: dynamic. The legacy shorthand les: 2 selects it.
What it does. Measures the model coefficient from the resolved field each update rather than prescribing it, using the Germano identity and Lilly's least-squares contraction. The resolved velocity is filtered a second time at a wider test scale; the stress carried between the two scales is computable, and matching the model against it determines the coefficient. The full derivation is in LES Turbulence Closure.
When to choose it. When the coefficient should respond to the flow: transitional regions, near walls, or decaying turbulence, where a single constant is wrong somewhere in the domain.
Parameters it owns. dynamic_frequency -> -les_dynamic_frequency, plus the filter_width, test_filter, averaging, and clipping blocks documented below.
Interactions. constant_cs, vreman_coefficient, and wale_coefficient belong to the other models and are rejected here.
Storage. The coefficient field is stored in CS and checkpointed. It holds C, the factor multiplying Delta^2 |S|, which is Cs^2 in the classical notation - not Cs. Under clipping.mode: none it is signed, because a negative coefficient is backscatter.
Diagnostics. With diagnostics.enabled, <run.analysis.metrics>/les_coefficient.csv records the effective coefficient converted to Cs, its spatial spread, eddy-viscosity levels, and the fractions of the domain that were backscattering or limited before clipping. Those last two describe a pre-clipping state that no stored field preserves. diagnostics.cadence sets the interval, and diagnostics.yoshizawa_ci sets the constant in the reported subgrid kinetic energy; neither affects the solution. picurv summarize --plot les.cs_effective renders the coefficient history directly.
Evidence. Implemented, with unit coverage in tests/c/test_les.c: the model tensor is checked against its closed form on constant strain, the filtered product is pinned apart from the product of filtered factors, the procedure returns exactly zero on uniform flow, and global averaging is checked to give one coefficient per block. The averaging reduction is checked to be decomposition-independent in tests/c/test_mpi_kernels.c. examples/decaying_isotropic_turbulence exercises the model end to end; on a shortened 32^3 run of it the coefficient field comes out uniform to roundoff under averaging.mode: homogeneous, as averaging over a homogeneous set must, and Cs climbs monotonically from a random initial field. That is a trend check, not a validation - the run was not carried far enough for Cs to settle. Benchmark characterized - hom02-les-execution-2026-10-09: on HOM02 at 64^3 the whole-domain Cs settles without drift at 0.193-0.196 with homogeneous averaging and 0.181-0.183 with local averaging, and the unmodelled cutoff-band pile-up (+0.72 in mean log10) falls to +0.012 (homogeneous) and +0.024 to +0.038 (local). Externally validated - wmles-channel-retau1000-werner-2026-10-09: with homogeneous averaging, clamp, volume_weighted_box and the Werner wall model, the wall-modelled Re_tau ~ 1000 channel matches Lee & Moser (2015) to 1.1% RMS in bulk-unit mean velocity and to 0.91-1.02 in outer-layer resolved Reynolds shear stress, with Cs 0.133. Production exercised - humphrey-turbulent-bend-wmles-2026-10-09: global averaging through a turbulent 90-degree duct bend, 17,000 steps over two inlet lengths.
Limitations. The procedure needs a developed field to sample, so the coefficient is held at zero for the first two steps of a run started from rest. With averaging.mode: local the coefficient is noisy and the least-squares closure is formally inconsistent, since it assumes the coefficient is constant over the averaging set; prefer homogeneous wherever the flow has a homogeneous direction. On HOM02 the settled coefficient is 0.18-0.19, not Lilly's 0.16-0.17, and the resolved energy decay misses the 10% criterion of hom02-les-accuracy-2026-10-09 during start-up: homogeneous averaging peaks 18% high and ends 10% low, local averaging peaks about 23% high and ends within 2%.
Identity. model: vreman -> -les 3 -> VREMAN -> VremanEddyViscosity.
What it does. Vreman (2004): nu_t = c sqrt(B_beta / (alpha_ij alpha_ij)), with alpha_ij = du_j/dx_i and beta_ij the sum over the cell's three grid directions of the outer product of the velocity change across the cell in that direction, edge_m . grad(u). On an aligned Cartesian grid this is Vreman's beta_ij = sum_m Delta_m^2 alpha_mi alpha_mj; on a curvilinear grid the same change is measured along the cell's own edges, so the model is resolved per direction and does not depend on the grid's orientation. It vanishes in pure shear and in any locally two-component flow, so a laminar shear layer gets no eddy viscosity.
When to choose it. Wall-bounded or complex geometry with stretched cells and no homogeneous direction: it needs no test filter, no averaging and no coefficient field, and it does not collapse a cell that is fine in two directions and coarse in the third onto one scalar width the way every filter_width choice must. Prefer it over dynamic_smagorinsky with averaging.mode: local, whose coefficient is noisy and clipped on such geometry, and over constant_smagorinsky, which does not vanish in laminar shear.
Parameters it owns. vreman_coefficient -> -les_vreman_coefficient, the constant c, default 0.07: Vreman's c = 2.5 Cs^2 for Cs = 0.17, calibrated on decaying isotropic turbulence.
Interactions. filter_width is refused, since the model takes each direction's spacing from the cell's edges rather than from one width; the parameters of the other models are refused as well. Central convection is used, as for every LES model. The Clark gradient term is independent and may be combined.
Storage. No coefficient field; nu_t is recomputed from the resolved gradient every step and nothing is checkpointed on the model's behalf.
Diagnostics. The startup banner reports Vreman, its coefficient, and per direction (cell edges; filter_width unused). With diagnostics.enabled, les_coefficient.csv records the eddy-viscosity levels and subgrid energy; its coefficient columns are written as nan, because the model carries no Smagorinsky coefficient.
Evidence. Implemented, with unit coverage run by make unit-les: tests/c/test_les.c case wale-and-vreman-kernels checks the kernel against an independent evaluation of the published formula, requires zero in pure shear and the same value when the flow and the cell rotate together, and recovers a turned cell's edges from its metrics. A three-step flat-channel run completes with the model selected. Benchmark characterized - hom02-les-execution-2026-10-09: on HOM02 it ran to the last DNS spectrum at 64^3 and 32^3 and at 64^3 brought the cutoff-band pile-up from +0.72 to +0.142 in mean log10.
Limitations. The constant is an isotropic-turbulence calibration. The model is purely dissipative: it provides no backscatter. It responds to resolved velocity gradients, so in a laminar or transitional stretch it is small by design and does not by itself damp grid-scale oscillation that a non-dissipative convection scheme leaves behind. It is the least accurate of the four models on HOM02 at 64^3 (hom02-les-accuracy-2026-10-09): its low-band spectrum error, 0.107 RMS in log10, misses the 0.1 criterion, and its resolved energy runs 29% high early and ends 16% low.
Identity. model: wale -> -les 4 -> WALE -> WALEEddyViscosity.
What it does. Nicoud & Ducros (1999): nu_t = (C_w Delta)^2 (S^d_ij S^d_ij)^(3/2) / [(S_ij S_ij)^(5/2) + (S^d_ij S^d_ij)^(5/4)], where S^d is the traceless symmetric part of the squared velocity gradient. It vanishes in pure shear and falls as the cube of wall distance approaching a wall, the physically correct near-wall behaviour, without a damping function.
When to choose it. Wall-resolved LES where the near-wall eddy viscosity should vanish correctly without a damping function or a dynamic procedure. Against vreman it uses one scalar width, so pair it with filter_width: scotti on stretched cells; against dynamic_smagorinsky it needs no test filter or averaging and is well behaved on geometry with no homogeneous direction.
Parameters it owns. wale_coefficient -> -les_wale_coefficient, the constant C_w, default 0.5, within Nicoud & Ducros's isotropic calibration of 0.55-0.60.
Interactions. Takes filter_width like the Smagorinsky models; the parameters of the other models are refused. Central convection is used, as for every LES model. The Clark gradient term is independent and may be combined.
Storage. No coefficient field; nu_t is recomputed every step.
Diagnostics. The startup banner reports WALE, its coefficient and the filter width. With diagnostics.enabled, les_coefficient.csv records the eddy-viscosity levels, with its Smagorinsky coefficient columns written as nan.
Evidence. Implemented, with unit coverage run by make unit-les: tests/c/test_les.c case wale-and-vreman-kernels checks the kernel against an independent evaluation of the published formula, requires zero in pure shear, and requires the same value when the flow rotates. A three-step flat-channel run completes with the model selected. Benchmark characterized - hom02-les-execution-2026-10-09: on HOM02 it ran to the last DNS spectrum at 64^3 and 32^3, at 64^3 brought the cutoff-band pile-up from +0.72 to +0.052 in mean log10, and held the low-band error to 0.077.
Limitations. Unlike vreman, it does not vanish in solid-body rotation. The constant is an isotropic calibration, it is purely dissipative, and it is small in laminar flow by design, so it does not by itself damp grid-scale oscillation there. On HOM02 at 64^3 (hom02-les-accuracy-2026-10-09) the resolved energy runs 21% high early and ends 13% low.
filter_width sets how each cell's filter width Delta is derived from its metrics. It applies to both LES models, since both scale the eddy viscosity by Delta^2.
| Value | Maps to |
|---|---|
cube_root_volume | 0 |
geometric_mean | 1 |
max_edge | 2 |
scotti | 3 |
Identity. les.filter_width: cube_root_volume -> -les_filter_width 0. The default.
What it does. Sets Delta to the cube root of the cell volume.
When to choose it. Near-isotropic cells, where it is exact and cheapest.
Parameters it owns. None.
Interactions. None beyond scaling nu_t.
Diagnostics. No dedicated output; its effect appears in the eddy-viscosity level.
Evidence. Implemented, covered by tests/c/test_les.c case filter-width-models-separate-on-stretched-cell. Benchmark characterized - hom02-les-execution-2026-10-09: every isotropic HOM02 run, and the 64 x 64 x 32 width comparison (mean nu_t/nu 4.38, against 4.59 for scotti and 7.97 for max_edge). Externally validated - wmles-channel-retau1000-werner-2026-10-09. Production exercised - humphrey-turbulent-bend-wmles-2026-10-09.
Limitations. Underestimates the width on a stretched cell, because a geometric mean is dominated by the short directions. On the 2:1 cells of the HOM02 width comparison, under the constant model, it leaves a cutoff-band excess of +0.108 in mean log10.
Identity. les.filter_width: geometric_mean -> -les_filter_width 1.
What it does. Sets Delta to the geometric mean of the cell's extents along its own grid directions, each the cell volume over the area of the face pair it crosses.
When to choose it. Skewed cells, where those extents and the volume disagree. On an orthogonal cell the product of the extents is the volume, so this equals cube_root_volume exactly and adds nothing.
Parameters it owns. None.
Interactions. None beyond scaling nu_t.
Diagnostics. As above.
Evidence. Unit verified - the same unit case and filter-width-is-independent-of-cell-orientation, which requires the same width for a stretched cell before and after rotating it. Benchmark characterized - hom02-geometric-mean-equivalence-2026-10-09: on the 2:1 cells of the HOM02 width comparison it reproduced cube_root_volume in every reduced quantity, and the velocity fields differed by at most 1.3e-10 relative. The difference comes only from the two formulas' floating-point paths, because two identical runs on different nodes were bitwise equal.
Limitations. Still a geometric mean, so it shares the cube-root model's optimism on strongly stretched cells. Measured only on orthogonal cells, where it equals cube_root_volume by construction. Its behaviour on skewed cells, the one place it differs, has not been run.
Identity. les.filter_width: max_edge -> -les_filter_width 2.
What it does. Sets Delta to the cell's longest extent along its own grid directions.
When to choose it. When the model should act on the largest unresolved scale the cell can hold: a wall-modelled LES whose near-wall cells are deliberately coarse, a hybrid hybrid treatment, or a deliberately over-dissipative start-up.
Parameters it owns. None.
Interactions. None beyond scaling nu_t.
Diagnostics. As above.
Evidence. Implemented, covered by the same unit case. Benchmark characterized - hom02-les-execution-2026-10-09: on 64 x 64 x 32 HOM02 cells (aspect ratio 2) under the constant model it gives the largest eddy viscosity of the three widths (mean nu_t/nu 7.97, against 4.38 and 4.59) and removes the cutoff-band excess entirely (-0.026 in mean log10).
Limitations. The most dissipative of the three; on a near-isotropic grid it overestimates the width and adds subgrid dissipation the flow does not need. Unsuited to a wall-resolved LES: a wall-adjacent cell inherits the streamwise spacing as its width, so its eddy viscosity rises by the square of the cell aspect ratio, which can be several hundred, exactly where the resolved wall layer has to survive. On that run it ends with the resolved energy 10% above the DNS, where the other two widths end 10-12% below it.
Identity. les.filter_width: scotti -> -les_filter_width 3 -> LES_FILTER_WIDTH_SCOTTI -> ComputeCellFilterWidth.
What it does. Scotti, Meneveau & Lilly (1993): the cube root of the cell's extents along its own grid directions, multiplied by f(a1, a2) = cosh(sqrt((4/27)(ln^2 a1 - ln a1 ln a2 + ln^2 a2))), where a1 and a2 are the two shorter extents divided by the longest. f is exactly 1 for a cube and grows with the cell's distortion.
When to choose it. A wall-resolved mesh with moderately stretched cells, where cube_root_volume under-reports the unresolved scale and max_edge would inflate the width of every wall-adjacent cell to the streamwise spacing. It sits between the two: on the Humphrey bend mesh it raises the width by f = 1.06 at the duct centre, 1.34 at the cells 5-20 wall layers out where grid-scale oscillation appeared, and 2.1-2.2 in the wall-adjacent cells - an eddy viscosity 1.1, 1.8 and 4.4-4.8 times the cube-root value. geometric_mean and cube_root_volume apply no correction at all.
Parameters it owns. None.
Interactions. Applies to both Smagorinsky models. It changes the eddy viscosity only; the Clark gradient term takes its widths from the cell differences themselves and is unaffected by filter_width.
Diagnostics. No dedicated output. Its effect appears in the eddy-viscosity field and, with LES diagnostics enabled, in the nu_t levels of the coefficient log; the startup banner names the resolved width model as scotti.
Evidence. Implemented, with unit coverage run by make unit-les: tests/c/test_les.c case scotti-filter-width-matches-closed-form requires f = 1 for a cube, the closed-form width for a 1 x 2 x 8 cell, and the same width after rotating it; the filter-width-is-independent-of-cell-orientation case covers the shared extents. Benchmark characterized - hom02-les-execution-2026-10-09: on 64 x 64 x 32 HOM02 cells (aspect ratio 2) its eddy viscosity sits between the other widths, as its correction factor predicts (mean nu_t/nu 4.59, against 4.38 for cube_root_volume and 7.97 for max_edge), and its energy decay tracks cube_root_volume within 0.02.
Limitations. The derivation assumes the cut-off lies in the inertial range in every direction. Along the long side of a strongly stretched cell it reaches the energy-containing scales instead, so the correction loses its basis as aspect ratios grow. It is still a single width for a cell that is fine in some directions and coarse in another; a model that weights each direction by its own spacing, such as Vreman's, addresses that directly. A modest correction may also be too small to control grid-scale oscillation where the resolved flow supplies little strain. Only a 2:1 aspect ratio has been exercised against a reference.
averaging.mode selects the set the two Germano contractions are averaged over before they are divided. Lilly's closure assumes the coefficient is constant across that set, so the set should span directions in which the flow really is statistically homogeneous. Dynamic model only.
| Value | Maps to |
|---|---|
global | 2 |
homogeneous | 1 |
local | 0 |
Identity. les.averaging.mode: local -> -les_averaging_mode 0. The default.
What it does. Divides the two contractions cell by cell, giving a coefficient field that varies pointwise.
When to choose it. Geometry with no homogeneous direction, where no larger averaging set can be justified.
Parameters it owns. None.
Interactions. averaging.directions does not apply and is rejected.
Diagnostics. The coefficient spread columns in <run.analysis.metrics>/les_coefficient.csv show how noisy the field is.
Evidence. Implemented, covered by tests/c/test_les.c case average-ratio-local-is-pointwise. Benchmark characterized - hom02-les-execution-2026-10-09: on HOM02 at 64^3 it settles at whole-domain Cs 0.181-0.183 under all three clipping modes and ends closest to the DNS energy of any configuration (within 2% at t = 5.47).
Limitations. The denominator collapses wherever the resolved strain is briefly small, so the coefficient is noisy, and the least-squares derivation is formally inconsistent because the coefficient it assumes constant is not.
Identity. les.averaging.mode: homogeneous -> -les_averaging_mode 1.
What it does. Averages both contractions over the homogeneous directions, then divides. The directions come from averaging.directions when given, and otherwise from the case's resolved periodicity, which the loader derives from the boundary pairs and rejects the case unless opposite faces, and all blocks, agree on. A triply periodic box therefore yields one coefficient for the whole domain, and a channel periodic in i and k yields a wall-normal profile, in both cases with no extra configuration.
When to choose it. Whenever the flow has a homogeneous direction. This is the mode the dynamic procedure was derived for.
Parameters it owns. averaging.directions -> -les_averaging_directions, a subset of [i, j, k] overriding the periodic default.
Interactions. Naming a direction the case does not declare PERIODIC is accepted with a warning, since homogeneity is the user's claim to make. Requesting this mode with neither periodic pairs nor an explicit list is rejected.
Diagnostics. cs_effective in <run.analysis.metrics>/les_coefficient.csv is the whole-domain value regardless of mode, so it stays comparable across modes.
Evidence. Implemented, covered by tests/c/test_les.c cases homogeneous-averaging-derives-periodic-axes and average-ratio-retains-unaveraged-direction. Benchmark characterized - hom02-les-execution-2026-10-09: whole-domain Cs 0.193-0.196 on HOM02 at 64^3, identical to global there. Externally validated - wmles-channel-retau1000-werner-2026-10-09, averaging over the channel's two periodic directions.
Limitations. Averaging over a direction the flow is not homogeneous in smears real spatial variation into a single number.
Identity. les.averaging.mode: global -> -les_averaging_mode 2.
What it does. Averages both contractions over the entire block, giving one coefficient per block per update.
When to choose it. A domain you know is homogeneous but have not declared periodic. Where the domain is periodic in all three directions, homogeneous reaches the same answer without asserting anything the boundary conditions do not already say.
Parameters it owns. None.
Interactions. averaging.directions does not apply and is rejected.
Diagnostics. As above.
Evidence. Implemented, covered by tests/c/test_les.c cases average-ratio-divides-summed-fields and dynamic-procedure-global-average-is-uniform, and by the decomposition-independence case in tests/c/test_mpi_kernels.c. Benchmark characterized - hom02-les-execution-2026-10-09: identical to homogeneous to every printed digit on the triply periodic HOM02 box, as it must be. Production exercised - humphrey-turbulent-bend-wmles-2026-10-09: one coefficient for the whole turbulent 90-degree bend, Cs about 0.07.
Limitations. Discards all spatial variation in the coefficient, which is wrong wherever the flow is inhomogeneous.
clipping.mode bounds the coefficient the contraction produces. Dynamic model only.
| Value | Maps to |
|---|---|
clamp | 0 |
clip_negative | 1 |
none | 2 |
Identity. les.clipping.mode: clamp -> -les_clip_mode 0. The default.
What it does. Restricts the coefficient to [0, max_cs^2].
When to choose it. The conservative default, and the safe choice when a run is at risk of a diverging coefficient.
Parameters it owns. clipping.max_cs -> -les_clip_max_cs, a ceiling on Cs and not on the stored coefficient. Defaults to 0.3, roughly twice the physical value, so it catches divergence without shaping the ordinary distribution.
Interactions. Removes backscatter, so the total-viscosity floor never engages from below.
Diagnostics. limited_fraction in <run.analysis.metrics>/les_coefficient.csv reports the volume fraction the clip modified. A ceiling that is doing nothing reads near zero.
Evidence. Implemented, covered by tests/c/test_les.c case clip-model-coefficient-modes. Benchmark characterized - hom02-les-execution-2026-10-09, under homogeneous and local averaging. Externally validated - wmles-channel-retau1000-werner-2026-10-09. Production exercised - humphrey-turbulent-bend-wmles-2026-10-09.
Limitations. Discarding the negative tail is not neutral: the positives that would have cancelled it survive, so the mean subgrid dissipation is biased upward.
Identity. les.clipping.mode: clip_negative -> -les_clip_mode 1.
What it does. Discards negative coefficients but imposes no ceiling.
When to choose it. When backscatter is not wanted but a ceiling would distort a coefficient you have reason to trust at large values.
Parameters it owns. None; clipping.max_cs imposes no bound here, and naming it under this mode is rejected rather than ignored.
Interactions. As for clamp.
Diagnostics. As for clamp.
Evidence. Implemented, covered by the same unit case. Benchmark characterized - hom02-les-execution-2026-10-09: under local averaging on HOM02 at 64^3 it ran to the end, settled at whole-domain Cs 0.182, and differs from clamp by at most 0.01 in energy-decay error.
Limitations. Carries the same upward bias in mean dissipation as clamp, without the divergence guard.
Identity. les.clipping.mode: none -> -les_clip_mode 2.
What it does. Keeps the signed coefficient, so cells where the resolved scales are receiving energy from the unresolved ones - backscatter - are represented rather than zeroed.
When to choose it. When the physics of backscatter matters, or when the upward bias the clipping modes introduce in mean dissipation is itself the thing under study.
Parameters it owns. None directly, but it is the mode that makes clipping.min_viscosity_ratio -> -les_min_viscosity_ratio load-bearing: the total viscosity is floored at ratio * nu, which bounds the quantity that actually has to stay positive for the momentum operator to remain well posed.
Interactions. The stored coefficient field becomes signed. Anything reading CS must expect negative values.
Diagnostics. backscatter_fraction in <run.analysis.metrics>/les_coefficient.csv reports the volume fraction with a negative coefficient, which is exactly what the other two modes discard.
Evidence. Implemented, covered by tests/c/test_les.c cases clip-model-coefficient-modes and eddy-viscosity-floor-bounds-total-viscosity. Benchmark characterized - hom02-les-execution-2026-10-09: under local averaging on HOM02 at 64^3, keeping the signed coefficient, the run stayed stable to the last DNS spectrum and ended within 1% of the DNS energy.
Limitations. A locally averaged coefficient is negative at a large fraction of points in developed turbulence, so this mode leans hard on the viscosity floor. Pair it with averaging.mode: homogeneous, where the averaged coefficient is negative only when the whole homogeneous set is backscattering on balance. Stability has been shown for one decaying isotropic flow only.
| Value | Maps to | Status |
|---|---|---|
box | 0 | accepted spelling of volume_weighted_box |
simpson_ik | 1 | supported |
volume_weighted_box | 0 | supported |
Identity. les.test_filter.kernel: volume_weighted_box -> -les_test_filter_kernel 0. Accepted spelling: box. The default.
What it does. Averages the 3x3x3 stencil with cell-volume weights, so neighbouring cells of different size contribute in proportion to the volume they represent. Makes no assumption about which directions are homogeneous, and excludes solid cells.
When to choose it. The general-purpose choice, and the only defensible one on a geometry with no homogeneous direction.
Parameters it owns. None. test_filter.width_ratio belongs to the block, not to the kernel, and applies to either kernel.
Interactions. Consulted only by dynamic_smagorinsky.
Diagnostics. No dedicated output; its effect reaches <run.analysis.metrics>/les_coefficient.csv through the coefficient.
Evidence. Implemented, covered by tests/c/test_solver_kernels.c, which checks that it preserves a constant field and returns zero when the stencil is entirely solid. Every dynamic run of three campaigns used it, at width ratio 2: benchmark characterized - hom02-les-execution-2026-10-09; externally validated - wmles-channel-retau1000-werner-2026-10-09; production exercised - humphrey-turbulent-bend-wmles-2026-10-09.
Limitations. Less accurate than a Simpson stencil where one is admissible.
Identity. les.test_filter.kernel: simpson_ik -> -les_test_filter_kernel 1.
What it does. Applies a two-dimensional Simpson stencil across the central eta-plane of the 3x3x3 neighbourhood, weighting the nine samples 1, 4, and 16. Volume weights are not used: the stencil assumes uniform spacing in the plane it averages over.
When to choose it. A plane channel or any flow homogeneous in xi and zeta, where the higher-order stencil is both admissible and more accurate.
Parameters it owns. None.
Interactions. The homogeneity assumption is now checked rather than trusted: selecting this kernel without declaring both xi and zeta PERIODIC is rejected during validation. test_filter.width_ratio applies to the two directions the stencil filters and leaves eta at the grid width, so the dynamic procedure uses alpha = ratio^(4/3) here rather than the box filter's ratio^2. The default ratio 2 therefore gives a cube-root width ratio of 4^(1/3), about 1.59.
Diagnostics. As above.
Evidence. Unit verified - make unit-les: tests/c/test_solver_kernels.c checks the stencil alongside the box filter, and tests/c/test_les.c (dynamic-procedure-simpson-ratio-counts-filtered-directions) runs the dynamic procedure on a linear field and requires the coefficient to scale as 1/(ratio^(4/3) - 1), which the former squared-ratio weighting fails. Benchmark characterized - hom02-simpson-ik-2026-10-09: on HOM02 the former weighting halved the dynamic coefficient against the box filter (0.091 against 0.194) and left a +0.39 cutoff-band pile-up; weighted as a two-direction filter it gives 0.168 and +0.12, and runs to the last DNS spectrum. Externally validated - wmles-channel-retau1000-simpson-2026-10-09: on the wall-modelled Re_tau ~ 1000 channel, with the corrected weighting, u_tau is 2.5% above Lee & Moser, the bulk-unit mean velocity is within 0.8% RMS, and the outer-layer resolved shear stress is 0.97-1.01 of the reference; the outer u' excess falls from 16% under the former weighting to 6%.
Limitations. Averages over only the central eta-plane, so it ignores variation in the wall-normal direction entirely; that is the point on a channel and wrong anywhere else. Even corrected, it is less dissipative than the box filter: on the channel it carries about 20% less eddy viscosity and 6% more outer u', and on isotropic turbulence ten times the box filter's cutoff pile-up. The channel criteria do not discriminate test filters, because the wall model sets the friction velocity; the corrected default was represented there by a width ratio equal to it within 1.6e-6 in alpha, not run on the corrected build.
| Value | Maps to | Status |
|---|---|---|
cabot | 3 | experimental |
log_law | 1 | supported |
loglaw | 1 | accepted spelling of log_law |
werner | 2 | supported |
werner_wengle | 2 | accepted spelling of werner |
A wall model supplies the stress of a boundary layer the mesh does not resolve, so it is only meaningful when the unresolved motions are modelled somewhere. One pairing is therefore refused before a run starts.
A wall model on a laminar case is refused for the same reason from the other direction: these laws describe a turbulent boundary layer, and below transition there is no inertial region for them to represent.
Where the model's stress actually enters is worth stating, because it is not obvious. The correction sets the near-wall velocity, and the momentum equation reaches the wall through a viscosity times a velocity gradient - so the modelled stress arrives only if that viscosity is the one which reproduces it, nu_eff = tau_w y / u. Molecular viscosity alone delivers a fraction u+/y+ of the stress, which at y+ = 267 is about a fourteenth of it. The wall model therefore installs its own effective eddy viscosity at its wall face, in place of the subgrid value, which is zero there in a wall-resolved run. nu_wall_over_nu_mean in <run.analysis.metrics>/wall_model.csv reports it.
Across a restart. A wall model keeps no history. Its friction velocity is written to every checkpoint as Utau, for scoring and post-processing; the wall-face viscosity is derived state and is not. On restart the first boundary pass recomputes both from the restored velocity, so a restarted run continues with the same law without any carried state. The Re_tau ~ 1000 channel record cited under werner is a restart of this kind, from a checkpoint of a run built with the previous relation.
Identity. turbulence.wall_function.model: log_law -> -wallfunction. Accepted spelling: loglaw. It is also the default when wall_function.enabled is true and no model is named.
What it does. Applies a logarithmic law-of-the-wall boundary treatment instead of resolving the viscous sublayer.
When to choose it. When the near-wall grid is too coarse to resolve the sublayer and you accept a modelled wall stress rather than a computed one.
Parameters it owns. wall_function.roughness_height -> -wall_roughness.
Interactions. Configured independently of the LES closure - enabling a turbulence model does not imply a wall function, and vice versa. The wall handler itself remains noslip; the wall function changes how the stress is imposed, not the BC selection.
The correction also reaches the LES closure. Contra2Cart rebuilds the Cartesian field from the contravariant one at the top of each timestep and so discards the previous solve's near-wall correction; the wall model is re-applied before the subgrid strain rates are formed, so the closure and the momentum equation agree about the near-wall velocity rather than disagreeing within one timestep.
Diagnostics. The startup banner reports whether wall functions are enabled and the configured roughness height. Every timestep appends one row to <run.analysis.metrics>/wall_model.csv - the wall-face mean, spread, minimum and maximum of the friction velocity, the mean and maximum first-cell y+, the mean wall distance, and nu_wall_over_nu_mean - and logs the mean friction velocity and y+ to the console.
The same row is checked against the range the selected law supports: mean first-cell y+ from 30 to 300 for log_law, at most 300 for werner, and at most 1000 for cabot. Each step outside it logs a warning, and ten consecutive steps outside it stop the run; a step back in range resets the count, so a startup transient survives where a mis-sized first cell does not.
Evidence. Implemented, with unit coverage run by make unit-runtime and make unit-boundaries. tests/c/test_runtime_kernels.c covers the velocity laws and their friction-velocity root-finds, each checked to invert its own law; tests/c/test_boundaries.c covers the integration, checking that the corrected first interior velocity is the modelled profile at that cell's wall distance, for the friction velocity the integration itself stored. Externally validated - wmles-channel-retau1000-loglaw-2026-10-09: on the wall-modelled Re_tau ~ 1000 channel (driven_channel/les_wallmodel_retau1000, restarted from the werner run), u_tau over the statistics window is 0.3% below Lee & Moser (2015), the driving-force balance agrees to 0.2%, U+ stays within 0.08 of the reference over y+ 100-500, and the outer-layer resolved Reynolds shear stress is 0.92-0.97 of it - every criterion the werner model was held to, with the widest margins of the three wall functions.
Limitations. Validity depends on the first cell falling in the logarithmic region. Only the wall-face mean y+ is checked, so individual cells outside the region, near a corner or a separation for instance, pass unremarked while the mean stays in range. The validation is one smooth-wall channel on one grid; roughness_height is not exercised, and the run adapted for 200 time units from a werner-developed state rather than developing under this law from rest.
Identity. turbulence.wall_function.model: werner -> -wallfunction 2. Accepted spelling: werner_wengle.
What it does. Applies the Werner-Wengle power-law wall model. The two-layer profile
y+ = 11.81, u+ = 8.3 (y+)^(1/7) above - is evaluated pointwise. The wall stress comes from inverting it at the reference point, the centre of the second cell from the wall, where the resolved speed is a point value; the boundary cell's speed comes from the same profile at its own centre. Both directions are closed form, with no root-find in either region, and because one monotone profile is used both ways a cell nearer the wall is always assigned the slower speed.When to choose it. When the first cell may fall inside or near the viscous sublayer, where a pure log law has no valid branch. Its explicit form also avoids the inner iteration the log law needs, which matters for a matrix-free Jacobian: an iterative inner solve makes the residual only piecewise smooth.
Parameters it owns. None. It has no roughness formulation, so roughness_height is rejected rather than accepted and ignored - a rough wall is not representable through this model.
Interactions. As for log_law.
Diagnostics. As for log_law; the banner reports the selected model by name.
Evidence. Implemented, with unit coverage run by make unit-runtime and make unit-boundaries. In tests/c/test_runtime_kernels.c, find_utau_Werner inverts u_Werner exactly at y+ from 2 to 500, across the switch, and the wall function given a reference speed taken from the profile returns that profile's friction velocity and places the boundary cell on it. The integration that dispatches to it is covered in tests/c/test_boundaries.c. Externally validated - wmles-channel-retau1000-werner-2026-10-09: the cell-integrated relation it replaced read the point reference speed as a first-cell average and put u_tau 14.5% above Lee & Moser (2015) on the wall-modelled Re_tau ~ 1000 channel (driven_channel/les_wallmodel_retau1000); rerun with the pointwise relation, u_tau over the statistics window is 2.9% above, and the outer-layer resolved Reynolds shear stress falls from 1.27 to 0.91-1.02 of the reference. Production exercised - humphrey-turbulent-bend-wmles-2026-10-09: a turbulent 90-degree duct bend at Re_D = 40,000, where the low-shear inner wall takes individual cells into the linear branch while the run continues.
Limitations. No roughness. The power-law constants are fixed, and the model carries no pressure-gradient term, so it describes an equilibrium layer only; no implemented model handles a non-equilibrium layer correctly (see cabot). The validation is one grid of one channel, and its friction velocity is biased about +3%: a later window of the same flow, wmles-channel-retau1000-werner-window2-2026-10-09, gave +3.2% and so missed the 3% criterion by 0.2 points while passing every other one. The driving force, now logged, agrees with the wall model's stress to 0.2%, so the 2% gap an outer-layer stress fit once suggested came from that fit's single-snapshot subgrid estimate, not from the model.
Identity. turbulence.wall_function.model: cabot -> -wallfunction 3.
What it does. Applies Cabot's wall model, which integrates a mixing-length eddy viscosity across the wall layer rather than assuming a profile shape.
When to choose it. Not for results yet. It is meant for near-wall flow that departs from equilibrium, but as implemented its pressure-gradient term makes the wall stress unreliable (see Limitations); use werner or log_law.
Parameters it owns. None currently.
What it needs. Cabot's departure from an equilibrium profile is driven by the wall-parallel pressure gradient, which is taken from the resolved pressure at the near-wall cell through ComputeScalarFieldDerivatives. The pressure is the previous projection's, the same lag every other explicit use of it carries.
Interactions. As for log_law.
Diagnostics. As for log_law.
Evidence. Implemented, with unit coverage run by make unit-runtime and make unit-boundaries: find_utau_Cabot and u_Cabot in tests/c/test_runtime_kernels.c. The integration that dispatches to it is covered in tests/c/test_boundaries.c. The one reference-flow comparison failed: wmles-channel-retau1000-cabot-2026-10-09. Restarted on the wall-modelled Re_tau ~ 1000 channel from a state werner held at u_tau 0.0514, it gave 0.039 from the first step, with 3.4 times werner's cell-to-cell scatter, and it was cancelled before its statistics window.
Limitations. Not usable as implemented. The pressure-gradient term integrates the instantaneous resolved gradient across the layer up to the reference cell, without the convective terms that balance it in the full boundary-layer equations; on the channel it was +-38% of the reference velocity, gave a negative stress in a third of wall cells, and the friction-velocity solve takes the magnitude of that stress. Evaluated offline on the same state, the relation without its pressure term gives -2.6%. No roughness formulation, so roughness_height is rejected rather than accepted and ignored. The mixing-length constant is fixed.
Syntax: a list of 6 face entries, one per face.
Supported face names:
-Xi, +Xi, -Eta, +Eta, -Zeta, +ZetaSupported type/handler combinations:
INLET + constant_velocity (vx/vy/vz)INLET + parabolic (v_max)INLET + prescribed_flow:params.source.type: file, params.source.pathparams.source.type: generated, params.source.generator: square_duct_poiseuilleparams.source.type: field_slice, params.source.field_file, params.source.grid_file, params.source.slicegenerators/profile.gen; optional params.source.script selects a compatible overrideOUTLET + conservationWALL + noslipPERIODIC + geometricPERIODIC + constant_flux (target_flux, optional enforce_seam_flux; apply_trim is a deprecated alias)PERIODIC + initial_flux (no params; optional enforce_seam_flux) - target measured from the initial conditionAll six faces must be explicitly provided for each block. For detailed handler semantics, validation constraints, and C dispatch path, see Boundary Conditions Guide.
Generated profile example:
picurv run --solve generates the dimensional .picslice, writes profile.info, stages the solver-scale .picslice, and passes the existing source_file key to the C runtime. Use picurv precompute --case ... to create the same deterministic artifacts without launching the solver.
Field-sliced profile example:
field_slice uses Python preprocessing to write a normal dimensional PICSLICE; the C runtime still sees only the staged source_file.
Optional escape hatch for flags not yet exposed in structured schema:
Use sparingly and prefer structured keys when available.
case.yml is designed to be combined with reusable profiles for the other config roles.
Common patterns:
case.yml + multiple monitor.yml files (debug vs production output),case.yml + multiple post.yml recipes (quick scalar check vs heavy VTK/statistics),solver.yml reused across many case.yml files,cluster.yml reused across many runs and sweeps.For worked combinations, see Workflow Recipes and Config Cookbook.
Proceed to Configuration Reference: Solver YAML.
Cross-file contract/mapping: