Kitchen sinkΒΆ
Here a full example of the configuration file is provided, with its fields and capabilities.
Note
In each models.BC.BC_map (and models.scalar_transports.<name>.BC.BC_map) entry, the structural keys (pos, system, BC, wall_normal, order) stay at the top level and the BC-specific parameters live under a nested params: field, validated at parse time against the selected BC. The entry is extra="forbid", so a misspelled or misplaced key is rejected on load; BCs that take no parameters omit params: entirely. Fluid and scalar entries share the same placement grammar: pos takes a cardinal alias, a boolean predicate over x, y, z or a typed shape, system names the domain.systems frame it is written in, and wall_normal is the explicit outward cardinal direction required by every scheme that reconstructs along the wall normal.
Note
A boundary condition, a models.volumetric_regions entry and a scalar source_regions entry all name their region of space with the same pos, which takes a cardinal alias, a boolean predicate or a typed shape (kind: point, points, line, plane, box, predicate). See the boundary-condition guide for the per-kind fields.
# ============================================================
# Nassu kitchensink configuration
# This file demonstrates every available configuration field.
# Use it as a reference when building your own .nassu.yaml.
# ============================================================
# Load parent configurations from other files before this one is parsed.
# Simulations defined in those files can be used as parents here.
# The path must be relative to your shell's current working directory,
# not to this file's location.
dependencies:
# sim_cfg_files: ["path/to/dependent/simulation_config.yaml"]
sim_cfg_files: [] # Empty list - no external dependencies
# Named values used throughout this file.
# Reference them with ${var_name} inside !sub and !math tags.
# Nested keys are accessed with dot notation: ${cfg_time.cst_step}
variables:
# Special built-in variables injected by Nassu at load time:
version: ${NASSU_VERSION} # x.y.z - running Nassu version string
filepath: ${NASSU_FILE_PATH} # docs/source/user_guide/02_config/file/kitchensink.nassu.yaml
foldername: ${NASSU_FILE_FOLDER} # docs/source/user_guide/02_config/file - directory containing this file
base_folder: "examples/study_cases/high_rise_building" # Root output path
domain_size: 96 # Base domain dimension in lattice nodes
height_ref: 8 # Reference building height in lattice units
# Nested variable maps - keys are accessed as ${cfg_time.develop_steps} etc.
cfg_time:
develop_steps: 5000 # Steps for flow to develop before statistics collection
# !math with variable references: computes one convective time unit (H/U)
cst_step: !math ${height_ref}/${u_ref[1]}
cst_run: 100 # Number of convective time units to run after development
# Multi-line !math expression - references other nested variables
total_steps: !math ${cfg_time.develop_steps} + ${cfg_time.cst_step} * ${cfg_time.cst_run}
# !math applied to a list - each element is evaluated independently
u_ref: !math [0.05, 0.05*1.1] # Two reference velocities in lattice units
angle_use: !math cos(pi*45/180) # 45-degree wind angle in radians
nested_angle: !math cos(acos(pi/4)) # Nested trigonometric expression
min_max: !math min(cos(acos(pi/4)), acos(pi/4)) # min() across two expressions
# YAML anchor for a macroscopic rescale block - define once and reuse with *MACRS_RESCALE.
# Converts lattice values to physical units for output: result = value * mul + cte
macrs_rescale: &MACRS_RESCALE
rho: {mul: 1e3, cte: 100} # Multiply density by 1000 (lattice rho to kg/m3)
ux: {mul: 100, cte: 0} # Scale x-velocity to physical units
uy: {mul: 100, cte: 0} # Scale y-velocity to physical units
uz: {mul: 100, cte: 0} # Scale z-velocity to physical units
simulations:
- name: highRiseBuildingZero # Unique simulation name within this file
# False to skip this simulation without removing it from the file.
# Defaults to true
run_simul: true
# Total number of LBM time steps to advance.
n_steps: 50000
# !sub substitutes ${var_name} references - use it for any string field
# that needs variable interpolation.
save_path: !sub "${base_folder}/resuts/high_rise_building"
# Controls how often Nassu prints simulation progress to stdout.
report:
start_step: 0 # Step at which reporting begins (0 = from the start)
end_step: 0 # Step at which reporting stops (0 = until the end)
frequency: 1000 # Report simulation progress every 1000 steps
warmup_steps: 0 # Initial steps excluded from the final full-run MLUPS
# (performance warmup cutoff; 0 keeps the full-run average)
# ----------------------------------------------------------------
# Domain
# ----------------------------------------------------------------
domain:
# Lattice node counts for the full domain box.
# All dimensions must be divisible by block_size.
domain_size:
# !unroll expands this simulation into multiple variants, one per list
# element. All !unroll lists in the same simulation block must have the
# same length. Here two variants are produced: 96^3 and 192^3 domains.
x: !unroll [!math "${domain_size}", !math "${domain_size}*2"]
y: !unroll [!math "${domain_size}", !math "${domain_size}*2"]
z: !unroll [!math "${domain_size}", !math "${domain_size}*2"]
# Alternative to domain_size: size the level-0 domain by a PHYSICAL EXTENT
# given in a coordinate system (see `systems` / `default_system` below). Each
# axis is rounded UP to a block_size multiple (with a warning) and the realized
# extent is reported in info.yaml. Provide either domain_size or domain_extent;
# a matching domain_size alongside it (e.g. from a dumped config) is accepted.
# domain_extent: [300.0, 200.0, 150.0] # in domain_extent_system coordinates
# domain_extent_system: site # defaults to default_system; non-rotated
# Output coordinate frame: to write an export's coordinates in physical units,
# declare a coordinate `system` below and select it per export via
# `data.exports.<name>.system` (and per monitor via `system`). There is no
# global output coordinate rescale.
# Size of each lattice block in nodes per edge.
# Changing this affects GPU shared-memory usage - leave as 8 unless advised.
block_size: 8
# Block-forest serialization. Build a refined grid once, save it,
# and reuse it across runs to skip the (potentially expensive) re-refinement.
#
# block_forest_save: write the constructed forest (after the refinement program
# below runs) to a `.nassu-bf.json` file. Commented out here; uncomment to cache.
# block_forest_save: !sub "${NASSU_FILE_FOLDER}/grids/cached.nassu-bf.json"
#
# block_forest_load: load the grid from a `.nassu-bf.json` instead of building it.
# The declared `domain_size`, `block_size` and periodic dimensions must match the
# file (mismatch is an error). Any `refinement.static` below is applied ON TOP of
# the loaded forest, so a cached grid can be reused and refined further.
# block_forest_load: !sub "${NASSU_FILE_FOLDER}/grids/cached.nassu-bf.json"
# Named coordinate systems. Each entry is a similarity
# transform - isotropic `scale`, `rotation` (radians) and `translation`,
# all about `fixed_point` (scale -> rotate -> translate, same convention as
# a body `transformation`) - relative to a `parent` system (default "lbm").
# Positioned objects (bodies, point clouds, refinement volumes) can
# tag the frame their coordinates are given in via a `system` field; the
# config layer composes the chain down to the base "lbm" lattice frame at
# setup. The "lbm" name is reserved (the identity) and cannot be redefined.
# `scale` is a single isotropic scalar because the lattice is cubic;
# per-axis scale is rejected. A config that declares no systems keeps
# everything in "lbm".
systems:
# A site frame in metres: 1 m -> 4 lattice nodes, origin shifted into
# the domain. Parent defaults to "lbm".
site:
scale: 4.0
translation: [16.0, 8.0, 0.0]
# A building frame chained onto `site` (parent: site -> lbm): its own
# offset and yaw are expressed in site (metre) coordinates and resolve
# through `site` down to the lattice frame.
building:
parent: site
scale: 1.0
translation: [10.0, 6.0, 0.0]
rotation: [0.0, 0.0, 0.7853981634] # 45 deg yaw about z
fixed_point: [0.0, 0.0, 0.0]
# Coordinate system used by positioned objects that do not set their own
# `system`. Defaults to "lbm" (the base lattice frame).
default_system: lbm
# Static (non-adaptive) mesh refinement rules.
# Each named entry under `static` is a user-chosen group name that
# combines any subset of: volumes_refine, bodies, volume_refinement_limit,
# volumes_not_refine. The names below are illustrative.
refinement:
static:
# Refinement group: refine a rectangular volume and a body simultaneously
volumes_body_refine:
# List of axis-aligned boxes to refine to a given level.
volumes_refine:
- is_abs: true # true = coordinates in absolute lattice units;
# false = coordinates relative to domain size (0 to 1)
lvl: 1 # Target refinement level for blocks in this box
start: [0.0, 0.0, 0.0] # Lower corner [x, y, z]
# !math on a list computes each element independently
end: !math [32.0, 160.0, "${domain_size}/2"] # Upper corner [x, y, z]
# Coordinate system the start/end corners are given in. Only
# is_abs: true volumes may use a non-lbm system, and that system
# must not contain a rotation (rotated/oriented refinement volumes
# are not supported). Here the box is given in the metre `site`
# frame and resolves to lattice coordinates at setup. Omit for "lbm".
system: site
# List of bodies whose bounding surfaces drive block refinement.
bodies:
- body_name: CAARC # Must match a key under domain.bodies
lvl: 4 # Refinement level to apply around this body
normal_offsets: [0] # Surface offset multipliers for refinement shell
# Optional per-body transformation applied before refinement lookup
transformation:
translation: [12.0, 0.0, 1.0] # Shift body for refinement only
# Refinement group: limit the spatial extent that any refinement can reach
limit_volume:
bodies:
- body_name: CAARC
lvl: 6
normal_offsets: [0]
# Clamp all refinement (volume and body) to this bounding box.
# No block outside this box is refined beyond level 0.
volume_refinement_limit:
is_abs: true
start: [0, 10, 0]
end: [40, 50, 150]
# Like refinement volumes, the limit box may be given in a coordinate
# system. Only is_abs: true, non-rotated systems are allowed (the
# limit box carries no orientation). Omit for "lbm".
system: site
# Refinement group: suppress refinement inside specified volumes
ignore_volumes:
bodies:
- body_name: CAARC
lvl: 6
# !range generates a numeric list: np.arange(start, end, step)
# End is exclusive. Generates from -0.5 to 4.9, doesn't include 5
normal_offsets: !range [-0.5, 5, 0.2]
- {body_name: CAARC.surface_name, lvl: 4, normal_offsets: !math [1/4]}
# Blocks fully inside any of these volumes are not refined.
volumes_not_refine:
- is_abs: true # Absolute lattice coordinates
start: [0, 10, 0]
end: [40, 50, 150]
# Exclude volumes accept the same coordinate `system`;
# is_abs: true, non-rotated only. Omit for "lbm".
system: site
- is_abs: false # Relative coordinates (fraction of domain size)
start: [0, 0.15, 0.1]
end: [0.1, 0.2, 0.3]
# Refinement group: exhaustive example using all available sub-fields
full_example:
volumes_refine:
- is_abs: true
lvl: 1
start: [0.0, 0.0, 0.0]
end: [32.0, 160.0, 64.0]
- is_abs: true
lvl: 4
start: [10, 20, 30]
end: [32.0, 160.0, 64.0]
# Oriented (rotated) volume: start/end define the box in a local frame
# and `transformation` maps it into the domain, refining a tight rotated
# box (e.g. aligned with an angled building). Requires is_abs: true when a
# rotation is given.
- is_abs: true
lvl: 4
start: [0, 0, 0]
end: [40, 20, 60]
transformation:
rotation: [0.0, 0.0, 0.7853981634] # 45 deg about z (radians)
translation: [60.0, 40.0, 0.0]
fixed_point: [20.0, 10.0, 30.0] # box centre
bodies:
- body_name: CAARC
lvl: 6
normal_offsets: [0]
volume_refinement_limit:
is_abs: true
start: [0, 10, 0]
end: [40, 50, 150]
volumes_not_refine:
- is_abs: true
start: [0, 10, 0]
end: [40, 50, 150]
- is_abs: false
start: [0, 0.15, 0.1]
end: [0.1, 0.2, 0.3]
# Point-cloud bodies defined by CSV files instead of .lnas geometry.
# The CSV must contain columns: x, y, z, nx, ny, nz, area
point_clouds:
tree: # User-chosen name for this point-cloud body
IBM:
# True to activate IBM for this point cloud
run: true
# IBM config name from models.IBM.body_cfgs
cfg_use: default
# IBM force-spread order. Bodies with the same order are processed
# together; lower order runs first.
order: 1
# Interval in time steps during which IBM is active.
# end_step: 0 = run until the simulation ends; start_step: 0 = from step 0
interval_run:
end_step: 0
start_step: 0
csv_path: fixture/point_cloud/sphere_100.csv # Path to point-cloud CSV
# STL/LNAS bodies for IBM. Each key is a user-chosen body name.
# !not-inherit prevents this dict from merging with any parent simulation's
# bodies field - the child's bodies list replaces the parent's entirely.
bodies: !not-inherit
CAARC: # User-chosen body name; referenced elsewhere by this key
IBM:
run: true # True to activate IBM for this body
cfg_use: building_cfg # IBM config name from models.IBM.body_cfgs
# Bodies with the same order are processed together;
# lower order runs first
order: 1
# Interval in time steps during which IBM is active.
# 0 means no limit is applied
interval_run:
end_step: 0
start_step: 0
# Band-only voxelization of the body surface as an alternative to IBM
# for the wall BC. Attaches a velocity BC to the surface band nodes with
# the wall normal snapped to the nearest cardinal.
voxelization:
run: false # True to voxelize this body's surface
# Band velocity BC alias: 'RegularizedHWBB'
BC: RegularizedHWBB
order: 0 # Order to apply the band BC (less is first)
# Voxel band radius. Default 1 seals every D3Q27 link crossing the
# wall; also sets the hole tolerance (gaps narrower than the band heal)
band_radius: 1
# Raise on band nodes with no acceptable cardinal normal (concave
# corners). Set False to skip such nodes instead.
strict_normal: true
# Scalar (advection-diffusion) BCs on the same band nodes, one entry
# per scalar field, independent of the velocity BC.
scalar_bcs:
# Zero-flux (adiabatic / impermeable) wall for the scalar field
# (regularized Neumann with zero prescribed flux).
- scalar: temperature
BC: ScalarRegularizedNeumann
J_w: 0.0
order: 0
# Fixed-value (Dirichlet) emission patch: prescribed wall value
# phi_w. The scalar imposes no velocity (the advection velocity
# comes from the fluid). Also accepts 'ScalarRegularizedNeumann'
# with 'J_w' for a fixed flux, or 'ScalarRegularizedRobin' with
# 'h' and 'phi_inf' for a convective heat-loss wall.
- scalar: smoke
BC: ScalarRegularizedDirichlet
phi_w: 1.0
geometry_path: fixture/lnas/wind_tunnel/CAARC.lnas # Path to .lnas geometry file
# Offsets (in lattice nodes) at which to add copies of this geometry,
# displaced along surface normals. Produces layered IBM shells.
# !range [-5, 1, 0.5] -> [-5.0, -4.5, -4.0, ..., 0.5]
normal_offsets_add: !range [-5, 1, 0.5]
# Height attributed to each surface triangle for volumetric algorithms.
# Defaults to 1
height_assume: 1
# Whether the surface height scales with refinement level.
# height_at_lvl = height_assume * 2^lvl
# Defaults to false
height_scales_per_lvl: false
# How to handle triangles smaller than area.min.
# Options are: "add" (keep them), "ignore" (discard them)
# Defaults to "error" (raise an exception)
small_triangles: "add" # | "ignore"
# Triangle area bounds (at level 0). Triangles above area.max are
# subdivided; triangles below area.min follow the small_triangles policy.
# max should be at least 4Γ min.
# Defaults to { min: 0.25, max: 1 }
area: {min: 0.25, max: 1}
# Clip triangles outside these bounding boxes at each geometry stage.
# Defaults to no limit (no triangles removed).
volumes_limits:
# Applied to raw geometry coordinates, before any transformation
raw:
- start: [10, 20, 0]
end: [100, 100, 100]
# Applied after this body's own transformation
body_transformed:
- start: [60, 40, 0]
end: [100, 200, 100]
# Applied after all transformations (including global_transformations)
full_transformed:
- start: [60, 40, 0]
end: [100, 200, 100]
- start: [100, 40, 50]
end: [150, 140, 70]
# Affine transformation applied to this body before it enters the domain.
# Order: translate to fixed_point -> scale -> rotate -> translate -> revert.
# rotation values are in radians.
transformation:
fixed_point: [0, 0, 0] # Pivot point for scale and rotation
rotation: [0, 0, 0] # Rotation angles [rx, ry, rz] in radians
scale: [1.0, 1.0, 1.0] # Per-axis scale factors
translation: [12.0, 0.0, 1.0] # Final translation [x, y, z]
# Coordinate system this body's coordinates are expressed in. The
# body's own `transformation` above is applied first, then
# the resolved system->lbm map. Here the geometry is positioned in the
# metre `site` frame and composed down to the lattice at setup. Omit
# (or set null) to use `domain.default_system`.
system: site
# Enable runtime motion for this body (basis for moving bodies / FSI).
# When true, the kernels apply runtime_transformation
# below to each node's reference position/normal in-kernel (the node
# buffer is never moved), and the node->block mapping is recomputed
# before the first interp/spread of each step. Leave false for static
# bodies (the static path is unchanged). Single refinement level only.
# Defaults to false
movable: false
# Runtime (current) body transformation, applied in-kernel to the IBM
# Lagrangian nodes' positions and normals during the run, relative to
# the loaded pose. Distinct from `transformation` above (baked in once
# at load). Only takes effect when movable is true; the motion driver
# writes this each step. Same fields as `transformation`;
# defaults to identity.
runtime_transformation:
fixed_point: [0, 0, 0]
rotation: [0, 0, 0]
scale: [1.0, 1.0, 1.0]
translation: [0.0, 0.0, 0.0]
# Optional prescribed time-series motion. Keyframes of
# (level-0 time step -> 4x4 pose), linearly interpolated in time and
# clamped outside the range. When set, the body moves: its pose is
# sampled at the current level-0 step each step (overriding
# runtime_transformation) and the IBM no-slip target becomes the
# boundary point velocity dx/dt (per-step finite difference, level-0
# time). Setting `motion` implies `movable`. Omit for a static body.
motion:
keyframes:
- time: 0 # level-0 step
transformation:
translation: [0.0, 0.0, 0.0]
- time: 5000
transformation:
translation: [25.0, 0.0, 0.0] # 25 lattice nodes over 5000 steps
cube_not_run: # Second body - IBM disabled for this one
IBM:
run: false # IBM is not computed for this body
cfg_use: terrain_cfg # Config name is still declared (for reference)
geometry_path: fixture/stl/basic/cube.stl
transformation:
fixed_point: [0, 0, 0]
rotation: [0, 0, 0]
scale: [1.0, 1.0, 1.0]
translation: [12.0, 0.0, 1.0]
# Clip all IBM nodes (bodies and point clouds) to this bounding box.
# Nodes outside this volume are discarded from the IBM kernel.
# Defaults to the full domain.
bodies_domain_limits:
start: [8.0, 4.0, 0.0]
end: [448.0, 156.0, 48.0]
is_abs: true # true = absolute lattice coordinates
# Apply the same affine transformation to multiple bodies, point clouds,
# and/or probe sets in one step. Transformations are applied after any
# body-specific ones and in list order.
global_transformations:
- transformation:
translation: [0, 0.5, 0] # [x, y, z] shift
fixed_point: [608.90625, 64, 0] # Pivot for rotation and scale
rotation: [0, 0, 3.141592654] # Rotate 180Β° around z-axis
scale: [1, 1, 1]
point_clouds_apply: [] # Point-cloud names to transform
bodies_apply: ["CAARC"] # Body names to transform
# Export paths to transform. Syntax:
# "volume.<export>" - all volume entities of an export (translate/scale only)
# "volume.<export>.<entity>" - a specific volume entity's box
# "series.<export>" - all entities in a series export
# "series.<export>.lines" - all lines in a series export
# "series.<export>.points.<name>" - a specific point
probes_apply: ["series.series1.lines", "series.series1.points.point1", "volume.field_volume.wake"]
# True to also apply this transformation to body-type probes
# (probes defined via a body_name) within the listed series.
# Defaults to false
apply_to_bodies_probes: false
- transformation:
translation: [50, -10, 0]
point_clouds_apply: []
bodies_apply: []
probes_apply: ["series.spectrum_probe.points.downstream", "series.spectrum_probe.points.upstream"]
# True to apply this transformation to bodies in probes series as well
apply_to_bodies_probes: true
# ----------------------------------------------------------------
# Checkpoint
# ----------------------------------------------------------------
checkpoint:
export:
# Interval at which to write checkpoint files to disk.
# No export when this block is absent.
interval: {end_step: 30000, frequency: 5000, start_step: 10000}
# True to write a final checkpoint when the simulation ends.
# Defaults to false
finish_save: true
# True to delete all but the most recent checkpoint on disk,
# saving storage at the cost of losing earlier restart points.
# Defaults to false
keep_only_last_checkpoint: false
load:
# True to resume from a saved checkpoint at startup.
# Defaults to false
checkpoint_start: true
# True to reset the time counter to 0 after loading the checkpoint fields.
# Useful for branching a new run from an existing flow state.
# Defaults to false
reset_time_step: false
# Path to the folder containing the checkpoint to load.
# When null, Nassu looks for the latest checkpoint in save_path.
# Defaults to null
folderpath: "path/to/checkpoint/folder"
# ----------------------------------------------------------------
# Data export
# ----------------------------------------------------------------
data:
# Runtime guards - the single mechanism for divergence / sanity control.
# Each guard watches a measurement (a cheap `field` peek or a `monitor`
# reduction), trips on a `condition`, and takes an `action` on its
# `interval`. With NO guards declared, an implicit default set applies
# the standard divergence behaviour: the fluid `rho` is checked
# `is_nonfinite -> stop` every 10 steps, each passive transported field
# `is_nonfinite -> freeze` (frozen and dropped from exports), and each
# coupled field `is_nonfinite -> stop`. A declared guard whose
# source targets a given field replaces that field's implicit default.
guards:
# Cheap NaN/Inf check on the fluid density, every 20 steps. Declaring
# this overrides the implicit fluid default (e.g. to change its cadence).
fluid_nan:
field: rho # peek a fluid macroscopic from one random block
condition: is_nonfinite # is_nonfinite | gt | lt | abs_gt | outside
action: stop # stop | freeze | warn | ignore
interval: {frequency: 20}
# Threshold guard backed by a monitor reduction (thresholds MUST be
# monitor-backed): warn if the domain-max density grows too large.
rho_cap:
monitor: rho_max # a declared `data.monitors.fields` entry
macr: rho # which of the monitor's macroscopics to read
stat: max # min | max | mean
condition: abs_gt # needs `value`; `outside` needs `lo`/`hi`
value: 5.0
action: warn
# Named export rescale/rename/time-rescale PROFILES. Each export (and
# monitor) selects one by name through its own `rescale` field; an export
# with no `rescale` applies no rescaling. Rescale, rename and time-rescale
# live only in these profiles, not on individual exports.
# Per-export macro SELECTION (`macrs` lists, statistics
# `macrs_1st_order` / `macrs_2nd_order`) stays on each export.
export_rescales:
physical: # profile name, referenced by an export/monitor `rescale: physical`
# Lattice-to-physical rescale per macroscopic: result = value * mul + cte.
# Keys are macroscopic names that exist in the config (base or derived).
macrs_rescale: *MACRS_RESCALE
# Rename macroscopic output names for readability. Keys are macroscopic
# names (rho, ux, uxuy ...), values are the exported names. A renamed name
# must not be a prefix of any derived field emitted by an export that
# selects this profile (renaming `ux` would alias `uxuy`/`uxuz`), so only
# `rho` is renamed here.
macrs_rename: {rho: pressure}
# Multiply the time-step index by this factor for the output time axis.
# Use the lattice-to-physical time ratio to convert to seconds.
# Defaults to 1.
time_rescale: 1
# Derived macroscopics: named SymPy expressions over base macroscopic
# instance names, declared once at the data root. A derived name can then
# be listed in ANY export's `macrs` (or a stats `macrs_1st_order` /
# `macrs_2nd_order`) and it flows through every output that export enables
# (instantaneous volume, statistics, series), evaluated from the
# base macroscopics it references - e.g. `<ux*uy>` for the Reynolds-stress
# cross components. Expressions reference only base instance names (no
# derived-on-derived) and the whitelisted math functions; a derived name
# must not collide with a built-in macroscopic name and need NOT appear in
# an export's `macrs` to be used (its bases are pulled in automatically).
derived_macrs:
uxuy: "ux*uy"
uxuz: "ux*uz"
uyuz: "uy*uz"
# Global field monitors - compute scalar statistics (min, max, mean)
# over the domain and write them to CSV at each interval.
monitors:
fields:
# monitor_name:
# ... specs
# Monitor 1: track the maximum density and its position
rho_max:
macrs: [rho] # Macroscopics to monitor (rho, u, S, etc.)
# Statistics to compute. Options: min, max, mean, pos
# pos reports the grid coordinate of the max/min value
stats: [max, pos]
interval: {start_step: 500, end_step: 10000, frequency: 50}
# Rescale, rename and time-rescale come from a `data.export_rescales`
# profile selected via this monitor's `rescale` field (omit for none).
# Monitor rows are buffered in memory and committed (CSV append +
# plot re-render) in batches every `interval_flush` steps. Lower values
# keep the on-disk files more up to date and reduce peak memory at the
# cost of more frequent disk I/O. The buffer is force-flushed at end of run.
# Defaults to 250
interval_flush: 250
# Monitor 2: min/max/mean of multiple macroscopics over a sub-volume
macrs_stats:
# u expands to ux/uy/uz; S expands to all Sij components
macrs: [rho, u, S]
stats: [min, max, mean]
interval: {start_step: 0, end_step: 0, frequency: 500}
# Restrict monitoring to blocks that overlap these boxes.
# If omitted, the full domain is monitored.
volumes_monitor:
- start: [10, 20, 0]
end: [100, 100, 100]
is_abs: true # Absolute lattice coordinates
# Optional coordinate system the corners are given in (default
# domain.default_system). Absolute, non-rotated systems only.
system:
- start: [0.1, 0.1, 0.1]
end: [0.9, 0.9, 0.9]
is_abs: false # Relative coordinates (fraction of domain size)
# Exclude blocks fully inside these boxes from the monitor.
volumes_ignore:
- start: [50, 60, 5]
end: [70, 120, 10]
is_abs: true
# Export IBM Lagrangian-node fields (interpolated velocity, spread force, ...)
# for each configured body, as an XDMF temporal collection.
export_IBM_nodes:
export_caarc:
body_name: CAARC # Must match a key in domain.bodies or domain.point_clouds
start_step: 0 # 0 = start from the first step
end_step: 0 # 0 = export until the simulation ends
frequency: 0 # 0 = export at every step (use with care - large output)
# How the nodes are exported. `triangle` (default) writes a
# triangle-conformed mesh: the topology is the source geometry's
# faces and each triangle carries one representative value (forces,
# area, normal, ...) aggregated from the runtime nodes on it.
# `point_cloud` writes the raw Lagrangian nodes and allows
# sub-sampling via `stride` / `target_num_nodes`. `triangle` is only
# valid for body geometries (point clouds must use `point_cloud`).
mode: triangle
# `stride` / `target_num_nodes` below apply to `point_cloud` mode
# only; setting them with `mode: triangle` is a configuration error.
# Export every Nth Lagrangian node. 1 keeps all nodes; 2 keeps
# nodes 0, 2, 4, ...; etc. Useful for sub-sampling bodies with
# very large node counts. Default 1.
stride: 1
# Desired approximate number of exported nodes. When set, it
# overrides `stride`: an effective stride is derived at the first
# export as max(1, round(total_nodes / target_num_nodes)). Leave
# unset (null) to use `stride` directly. Default null.
target_num_nodes:
# H5 rollover threshold in gigabytes; a new chunk is started as
# soon as the current one exceeds this size. Defaults to 4.0 GB.
max_h5_size_gigabytes: 4.0
# Number of (lvl-0) simulation steps between successive HDF5 writes.
# Per-step snapshots are accumulated in memory and committed to disk in
# batches every `interval_flush` steps. The buffer is also force-flushed
# at end of run and on checkpoint. Default 250.
interval_flush: 250
# Lean per-body skin-friction node export: a probe-style
# CSV/HDF time series of BC-independent wall-friction primitives, separate
# from the heavy `export_IBM_nodes` mesh export above. At its frequency a
# read-only friction-sampling pass populates the per-node friction fields
# (it spreads no force and never perturbs the flow), then the full
# post-processing set is written every time (no column selection).
body_nodes:
CAARC_friction:
body_name: CAARC # Must match a key in domain.bodies
start_step: 0
end_step: 0
frequency: 100
# `triangle` (default) aggregates the runtime Lagrangian nodes onto the
# body's source-geometry triangles (~10k faces instead of ~1M nodes):
# the intensive diagnostics become area-weighted means, `area` sums,
# normals come from the source triangle. `point_cloud` writes the raw
# per-node rows. `triangle` is body-only.
mode: triangle
# The export always writes the full post-proc set: the viscous
# `traction_x/y/z`, the friction velocity `friction_u_tau` /
# `friction_y_plus`, the wall surface `pressure`
# (p = rho*(1+theta)*c_s^2), the summed `area` and (triangle mode)
# `n_nodes`. The geometry columns (centroid `pos_*`, source-triangle
# `normal_*`) go to the static topology sidecar. The nonlinear scalars
# (tau_w, Cf, form/friction split) are derived later at read time.
# Steps between successive HDF5 writes; buffered between flushes and
# force-flushed at end of run and on checkpoint. Default 250.
interval_flush: 250
# Unified exports. Each named export picks one `target` (a volume OR a series
# entity collection) and one or more `outputs` (instantaneous / stats).
exports:
# Volume snapshot of the instantaneous field over a sub-volume.
field_volume:
macrs: ["rho", "u", "S"] # Macroscopics to include in each snapshot
rescale: physical # name of a `data.export_rescales` profile (omit for none)
# Coordinate frame the exported node coordinates are written in: a
# `domain.systems` name, or omit / null for lbm (lattice).
system: site
interval:
start_step: 78
end_step: 0 # 0 = run until the simulation ends
frequency: 10 # may be fractional
lvl: 0 # reference level, or the literal "max" for the finest level
constant_dt: true # default: uniform interval (frequency floored to whole finest steps); false = closest-snap
target:
# Named entities grouped by kind (volumes / lines / csvs / points /
# bodies / planes); kinds mix freely in one export.
volumes:
wake: # entity name; file stem <export>.volume.<entity>.<kind>
volume:
start: [32, 0, 0]
end: [448, 128, 64]
is_abs: true # absolute lattice coordinates
# Coordinate `system` the corners are given in.
# is_abs: true, non-rotated only; mapped to lattice at load. This
# is the INPUT frame for the box; the OUTPUT frame is the export's
# own `system` above. Omit for "lbm".
system: site
max_lvl: 3 # max refinement level to export; -1 exports all levels
max_h5_size_gb: 4.0 # HDF5 rollover threshold in GB
full: {} # a second volume entity: whole domain, all defaults
outputs:
instantaneous: true # write the raw field every interval step
# Running statistics over the same volume. By default the running averages
# are written as a single snapshot at the end of the run; set
# `stats.flush_interval` to also snapshot the partial averages periodically.
# `stats` macroscopics must be a subset of `macrs`; the selected profile's
# `macrs_rescale` is applied to the instantaneous value BEFORE accumulation.
field_stats:
macrs: ["rho", "u"]
rescale: physical # selects the `data.export_rescales` profile above
interval: {start_step: 78, end_step: 0, frequency: 10, lvl: 0}
target:
volumes:
field_stats:
volume: {start: [32, 0, 0], end: [448, 128, 64], is_abs: true}
outputs:
instantaneous: false # stats-only export (no raw field written)
stats:
# The derived names declared at `data.derived_macrs` can be listed
# directly in the order lists below to accumulate statistics of the
# per-step value of the expression - e.g. `<ux*uy>` for the
# Reynolds-stress cross components. They need NOT appear in this
# export's `macrs` (their bases are pulled in automatically).
macrs_1st_order: ["rho", "u", "uxuy", "uxuz", "uyuz"] # running mean
macrs_2nd_order: ["u"] # running mean of squares (std basis)
# How often (in level-0 steps) to snapshot the running averages to
# disk. 0 (default) = write only at the end of the run; a positive
# value (at least 100) also writes intermediate partial-average
# snapshots and flushes them on checkpoint.
flush_interval: 0
# Series export: a collection of point-like entities sampled by interpolation.
# All five entity kinds are shown; an export uses any subset.
series1:
macrs: [rho, u] # Macroscopics to sample
interval:
start_step: 78
end_step: 0
frequency: 10
lvl: 4 # sample on the finest level's time-step counter
interval_group: 1000 # steps per HDF table (series targets); default 1000
interval_flush: 250 # steps between disk flushes (series targets); default 250
target:
lines:
line1:
start_pos: [200.46875, 79.4285, 2.905] # Line start [x, y, z]
end_pos: [200.46875, 80.5715, 2.905] # Line end [x, y, z]
dist: 0.28575 # Spacing between sample points along the line
# Optional: name the coordinate system the endpoints are given in
# (default domain.default_system). Mapped to lbm before sampling.
# See domain.systems / domain.default_system.
system:
points:
point1:
pos: [200.46875, 79.4285, 2.905] # Point position [x, y, z]
system: # Coordinate system the position is given in
bodies:
# Sample at triangle centroids ("cell") or vertices ("vertex") of a
# named body at a given normal offset.
my_CAARC:
body_name: "CAARC" # Must match a key in domain.bodies
normal_offset: 0.03125
element_type: "cell" # or "vertex"
my_surface:
# Reference a named sub-surface as "body_name.surface_name"
body_name: "CAARC.surface_name"
normal_offset: -0.03125 # negative offset = inside the body
element_type: "cell"
csvs:
# Read positions from a CSV with a header row of columns x, y, z.
my_csv:
filename: "my_filename.csv"
planes:
# Axis-aligned plane (3D only), exported as a triangle surface so
# ParaView renders it as a surface. For an oriented plane, apply a
# rotation/translation via domain.global_transformations.probes_apply.
plane1:
axis: z # Plane normal direction (x, y or z)
axis_pos: 2.905 # Coordinate along `axis` where the plane sits
# In-plane bounds, ordered by global axis index excluding `axis`
# (axis=z -> (x, y)). Omit both min and max to span the full domain.
min: [200.0, 79.0]
max: [201.0, 81.0]
# Grid spacing: scalar (uniform) or a pair (one per in-plane axis).
dist: 0.28575
outputs:
instantaneous: true
# Max-rate point probe for FFT analysis: `interval: {frequency: 1, lvl: max}`
# samples each point at every finest-level internal iteration.
spectrum_probe:
macrs: [rho, u]
interval: {frequency: 1, lvl: max}
target:
points:
upstream:
pos: [200.46875, 80.0, 4.81]
downstream:
pos: [201.48375, 80.0, 4.81]
outputs:
instantaneous: true
# ----------------------------------------------------------------
# Models
# ----------------------------------------------------------------
models:
# GPU engine configuration
engine:
# Specific GPU device indices to use (e.g. [0, 1]).
# null lets Nassu pick devices automatically in ascending order.
# Defaults to null
devices_numbers:
# Number of GPU devices to use. Multi-device is not supported yet.
# Defaults to 1
n_devices: 1
# Compute engine.
# Options are: CUDA
name: CUDA
# Floating-point precision settings
precision:
# Precision used inside CUDA kernels for intermediate calculations.
# Options are: single, double, default
# Defaults to default
calculations: default
# Base precision - must always be specified explicitly.
# Options are: single, double
default: single
# Precision used for macroscopic field storage (rho, u, pi_neq, ...).
# Options are: single, double, default
# Defaults to default
macroscopics: default
# Precision used for LBM populations in GPU shared memory.
# Options are: single, double, default
# Defaults to default
populations: default
# Multiblock grid communication settings
multiblock:
# Refinement-interface communication operator.
# value_interp (default) - Lagrava-2012 value-interpolation coupling
# (cubic C2F prolongation, F2C point injection)
# conservative - conservative moment-transfer pair: positivity-
# preserving momentum-space C2F prolongation and
# averaged F2C restriction (forbids negative-rho
# at the interface near omega -> 2)
interface_coupling: value_interp
# Number of overlap nodes on the fine side of each Fine-to-Coarse interface.
# Defaults to 1
overlap_F2C: 2
# Per-level override for overlap_F2C. Key is the level number, value is the
# overlap count to use for that level's F2C communication.
# Defaults to {} (no per-level overrides)
custom_overlap_F2C:
1: 3 # Level 1 -> use overlap of 3
4: 2 # Level 4 -> use overlap of 2
# Mark coarse-side nodes inside F2C communication regions as unused
# to prevent undefined behaviour from uninitialised values.
# Defaults to true
mark_nodes_as_unused: true
# Optional adaptive checkerboard dissipation for near-inviscid
# (omega -> 2) refinement interfaces. A 2dx odd-even sensor gates a
# graded extra subgrid viscosity only where the grid-scale mode is
# present, so the resolved flow at the base Smagorinsky constant is
# untouched. Requires models.LES. Omit to disable (byte-identical).
interface_stabilization:
strength: 0.2 # extra-viscosity gain (a local Smagorinsky-constant boost)
threshold: 0.5 # sensor value (0 smooth, 1 grid-scale) above which it ramps in
# LES turbulence model
LES:
# Subgrid scale model to use.
# Options are: Smagorinsky
model: Smagorinsky
# Smagorinsky constant C_S. Typical CWE range: 0.10 to 0.17
sgs_cte: 0.17
# Generalized-Newtonian (non-Newtonian) rheology: a shear-rate-dependent
# apparent viscosity eta(gamma_dot), gamma_dot = |S|. Absent (or
# model: newtonian) keeps the constant-viscosity Newtonian solver
# byte-identical. The apparent viscosity enters only through omega (where
# the LES eddy viscosity enters); it can be combined with LES.
rheology:
# Model discriminator. Options are:
# newtonian (default, no-op), power_law, carreau_yasuda, herschel_bulkley
model: power_law
# Model-specific parameters (validated at parse time against `model`).
# Values are in level-0 lattice units (consistent with the base tau).
params:
# power_law: K, n, nu_min, nu_max (eta = K * gamma_dot^(n-1))
K: 0.05
n: 0.7
# Stability clip on the kinematic apparent viscosity (keeps omega in
# (0, 2)); required for power_law / herschel_bulkley, optional for
# carreau_yasuda (self-bounded).
nu_min: 1.0e-4
nu_max: 5.0e-1
# carreau_yasuda: eta_0, eta_inf, lambda_, a, n
# eta = eta_inf + (eta_0 - eta_inf) * [1 + (lambda_*gamma_dot)^a]^((n-1)/a)
# herschel_bulkley: K, n, tau_0, m_reg, nu_min, nu_max (Papanastasiou-
# regularized: eta = K*gamma_dot^(n-1) + tau_0*(1 - exp(-m_reg*gamma_dot))/gamma_dot)
# Lattice Boltzmann Method settings
LBM:
# Global body force vector applied uniformly across the domain [x, y, z].
# Non-zero values drive a pressure-gradient flow (e.g. channel flow).
F: {x: 0, y: 0, z: 0}
# Collision operator.
# Options are: RBGK (2nd-order Hermite), RRBGK (3rd-order, default for LES),
# HRRBGK (hybrid regularised)
coll_oper: RRBGK
# Operator-specific parameters (only relevant for HRRBGK)
coll_oper_params:
# Blending constant sigma for HRRBGK. Valid range: 0.95 to 1.0
# Defaults to 0.99
sigma_hrrbgk: 0.99
# Blending mode for HRRBGK.
# Options are: dynamic, constant
# Defaults to constant
mode_hrrbgk: dynamic
# True to activate the thermal model (variable theta β 0).
# The isothermal path (theta = 0) is the production default.
thermal_model: false
# Relaxation time tau = 1/omega. Controls kinematic viscosity:
# nu = cs^2 * (tau - 0.5) * dt. Must satisfy tau > 0.5 for stability.
# With RRBGK, values up to tau β 1 remain stable at high Re.
tau: 0.5000008125
# Optional independent bulk (volume) viscosity zeta (lattice units).
# When set, the trace of the non-equilibrium second moment relaxes at the
# constant rate omega_bulk = 1 / (zeta / cs^2 + 1/2) instead of the shear
# rate omega, damping the low-Mach reduced-pressure checkerboard mode.
# Omit (the default) to leave the scheme at its near-zero intrinsic bulk
# viscosity; the generated kernels are then byte-identical.
bulk_viscosity: 0.1
# Velocity set (lattice topology).
# Options are: D2Q9 (2D), D3Q15, D3Q19, D3Q27 (3D)
# D3Q27 is preferred for LES - complete 27-dimensional Hermite basis
vel_set: D3Q27
# Immersed Boundary Method settings
IBM:
# Discrete Dirac delta kernel width.
# Options are: 3_points (support Β±1.5 Ξx), 4_points (support Β±2 Ξx)
# Defaults to 3_points
dirac_delta: 3_points
# Minimum sum of Dirac delta weights for IBM to operate on a node.
# Use 0.99 to skip nodes near domain boundaries or multiblock transitions
# where the stencil is truncated. Lower values allow IBM everywhere.
# Defaults to 0.99
min_dirac_sum: 0.99
# Number of steps over which the IBM force ramps linearly from 0 to 100 %.
# Prevents a large transient impulse at startup.
# Defaults to 0 (no ramp)
forces_accomodate_time: 500
# Convergence threshold on the force change between IBM sub-iterations.
# A reasonable value for CWE is 1e-3 (β rho * u_ref^2).
# Defaults to 1000 (effectively no limit)
forces_spread_limit: 1e-3
# True to set IBM forces to zero at the start of each time step,
# so each step converges from scratch. False reuses the previous
# step's force as the initial guess (can speed up convergence).
# Defaults to true
reset_forces: false
# Named IBM configurations referenced by domain.bodies.IBM.cfg_use
body_cfgs:
# Built-in default config - empty means all sub-fields use their defaults
default: {}
# Configuration for a building body with an equilibrium TBL wall model
building_cfg:
# Number of IBM sub-iterations per time step.
# More iterations improve convergence at the cost of runtime.
# Defaults to 5
n_iterations: 5
# Multiply the computed IBM force by this factor before spreading.
# 1.0 = standard IBM; values < 1 damp the force.
# Defaults to 1
forces_factor: 1
# Allocate IBM force buffers (and run force processing) on every
# runnable block instead of only this body's precomputed affected
# set. Escape hatch for moving bodies (movable: true): a
# node may move into a block that was not originally affected, which
# would otherwise have no force buffer and silently drop the spread
# force. Uses more memory when on. Defaults to false
allocate_force_all_blocks: false
# Wall model - replaces conventional no-slip IBM near walls.
wall_model:
# Wall model type.
# Options are: EqLog (log-law), EqTBL (equilibrium turbulent
# boundary layer), NonEqTBL (non-equilibrium TBL with streamwise
# pressure-gradient term).
name: NonEqTBL
# Distance (in lattice nodes) from the wall at which to interpolate
# the tangential velocity used by the wall model.
dist_ref: 2
# Thickness of the IBM spreading shell (in lattice nodes).
dist_shell: 0.25
# Step at which to switch from conventional IBM to the wall model.
# Conventional IBM is applied for steps < start_step.
# Defaults to 1000
start_step: 1000
# Parameters specific to the chosen wall model.
params:
z0: 0.0001 # Roughness length in lattice units
TDMA_max_error: 5e-06 # TDMA solver convergence tolerance
TDMA_max_iters: 50 # Maximum TDMA iterations per node
# Number of TDMA grid divisions (must be an odd number)
TDMA_max_div: 25 # Maximum divisions
TDMA_min_div: 21 # Minimum divisions
# Target y+ for the first TDMA node nearest the wall
# Defaults to 0.2
TDMA_yp_target: 0.2
# Exponential wall-clustering strength (>= 0) for the TBL TDMA
# grid. The wall-normal solve uses the divisions on a uniform
# computational coordinate xi in [0, 1] mapped to the physical
# wall distance by n = dist_interp*(exp(beta*xi)-1)/(exp(beta)-1),
# clustering nodes toward the wall for better near-wall
# resolution at the same division count. The default 2.0 puts
# the first node well inside the viscous sublayer for a typical
# 10-50 wall-unit near-wall cell; 0.0 gives a uniform grid. The
# tridiagonal solver is unchanged - beta enters only through the
# grid metric.
TDMA_stretch_beta: 2.0
# NonEqTBL only: floor (> 0) on the previous-step friction
# velocity used to compute the LaRTE adaptive EMA coefficient
# on the streamwise pressure gradient,
# alpha_node = u_friction_lagged / (dist_ref + u_friction_lagged).
# The floor keeps alpha_node well-defined at start-up and at
# near-stagnation nodes. Default 1e-4 (lattice); a tighter
# case-specific guideline is one tenth of the log-law
# equilibrium friction velocity at the freestream scale.
NeqWM_u_friction_floor: 1.0e-4
# NonEqTBL only: static multiplier (>= 0) applied to the
# LaRTE-filtered pressure gradient right before it enters the
# TBL TDMA. The physically motivated value is the lattice
# Mach number itself, m = Ma_LBM = U_LBM * sqrt(3), which
# rescales the raw LBM `dp/ds` magnitude back to the
# incompressible-equivalent expectation of the TBL ODE - see
# `theory/wall_model/neq_pres_filter` for the derivation. In
# this kitchensink we hard-code 1.0 for explicitness, but
# case YAMLs typically derive it from a `Ma_LBM` variable as
# `NeqWM_pres_grad_mult: !math ${Ma_LBM}`. Setting it to 0.0
# zeroes the source seen by the TDMA (NonEqTBL then behaves
# like the equilibrium TBL) while pres_grad and
# pres_grad_filt keep updating - useful as a passive raw-
# signal sensor for spectral diagnostics.
NeqWM_pres_grad_mult: 1.0
# Configuration for a terrain body with a log-law wall model
terrain_cfg:
n_iterations: 3
forces_factor: 1
wall_model:
# Equilibrium log-law wall model
name: EqLog
dist_ref: 2.5
dist_shell: 0.5
# Parameters specific to the EqLog model
params:
z0: 0.0001 # Aerodynamic roughness length in lattice units
# Optional flux-driven Monin-Obukhov stratified-surface-layer
# stability correction. Omitting it (the default) keeps the
# neutral log-law path unchanged. The buoyancy strength
# B = beta*|gravity| is read from the coupling scalar's `buoyancy`
# block, so `coupling_scalar` must name a buoyant
# `scalar_transports` entry.
# mo_stability:
# # Scalar the buoyancy strength B = beta*|gravity| is read from
# # (must declare `buoyancy`).
# coupling_scalar: temperature
# # Prescribed surface kinematic heat flux <w'phi'>_0 (lattice
# # units), the MO driver. Positive -> unstable, negative ->
# # stable, zero -> neutral.
# surface_heat_flux: 1.0e-6
# # Optional startup ramp: linearly ease the surface flux in from
# # zero (neutral) over this many coarsest-level steps, removing
# # the step-0 flux shock. 0 (default) applies it at full strength.
# ramp_steps: 0
# Configuration using the debug-only constant-force model
constant_body_cfg:
n_iterations: 1
forces_factor: 0.12
# Debug-only: apply a fixed body force rather than iterating to enforce a
# velocity. A tool for exercising the IBM force path, not a physical
# model; leave unset in production runs.
debug_cte_force:
# Target velocity magnitude imposed on the body normal direction.
constant_velocity: 1e-2
# Factor applied to the tangential component of the force.
# 0 = no tangential correction; 1 = drive tangential velocity to zero.
tangential_force_factor: 0
# True to use individual surface normals for force direction.
# False = use direction_apply for all nodes.
use_normal: false
# Global force direction when use_normal is false [x, y, z].
# The vector does not need to be normalised.
# Defaults to null
direction_apply: [0, 1, -1]
# Initial condition for the macroscopic fields at t = 0
initialization:
# Path to an .xdmf file from a previous simulation to initialise from.
# Nassu uses linear interpolation to map the field onto the current mesh.
# The file must contain at least rho and u; S is reconstructed via finite
# differences on u.
# Mutually exclusive with equations. Defaults to null.
# macrs_filename: ./fixture/macrs/multiblock_load.xdmf
# Equation-based initialization: SymPy expressions for rho, ux, uy, uz
# as functions of physical node coordinates x, y, z (lattice units).
# Allowed functions: sin, cos, tan, asin, acos, atan, atan2, log, log2, log10,
# exp, sqrt, Abs, ceil, floor, Min/min, Max/max.
# Defaults: rho="1", ux="0", uy="0", uz="0"
equations:
rho: "1"
ux: "0.05 * (z / 100.0) ** 0.25"
uy: "0"
uz: "0"
# Inlet field initialization: prefill the whole domain with the active
# inlet method's mean velocity profile ux(z) (homogeneous in x and y),
# instead of a constant. Requires an inlet turbulence method
# (models.BC.SEM). Defaults to false.
# (The name `sem_field` is accepted as an alias.)
inlet_field: false
# Boundary conditions applied at each domain face
BC:
# Which domain faces use periodic BCs [x, y, z].
# A true entry means the face pair at x=0 and x=N_x are periodic.
periodic_dims: [false, false, false]
# Global TDMA parameters shared across all wall-model BCs that use TDMA.
# Individual IBM wall models can override these with their own params block.
WM_cfg:
TDMA_max_error: 5e-06 # Convergence tolerance for TDMA
TDMA_max_iters: 50 # Maximum iterations per TDMA solve
# Number of TDMA divisions (must be an odd number)
TDMA_max_div: 25
# Ordered list of BCs to apply. BCs with the same order are applied
# simultaneously; conflicts are resolved in list order (last wins).
# Available BC types:
# RegularizedHWBB - no-slip wall
# RegularizedVelocityWall - moving wall (alias RegularizedVelocityBB)
# UniformFlow - prescribed inlet velocity
# Neumann, RegularizedNeumannSlip - zero-gradient / free-slip
# RegularizedNeumannOutlet - zero-gradient outlet with fixed rho
BC_map:
# Boundary condition name (see available types above)
- BC: RegularizedNeumannOutlet # Zero-gradient outlet
# Order in which to apply the BC. 0 runs first, then 1, then 2.
order: 2
# Face position. Options: E (x=N), W (x=0), N (y=N), S (y=0),
# F (z=N), B (z=0). Combine for edges/corners: NF, SW, etc.
pos: E
# Outward normal direction of this face (points out of the domain).
# Combine directions for edge/corner normals the same way as pos.
wall_normal: E
# BC-specific kwarg: target density at the outlet
params:
rho: 1.0
- BC: RegularizedNeumannSlip # Free-slip top face
order: 1
pos: F # z = N (top face)
wall_normal: F
- BC: RegularizedHWBB # No-slip ground (regularised halfway bounce-back)
order: 1
pos: B # z = 0 (bottom face)
wall_normal: B
- BC: RegularizedNeumannSlip # Free-slip north lateral face
order: 0
pos: N # y = N
wall_normal: N
- BC: RegularizedNeumannSlip # Free-slip south lateral face
order: 0
pos: S # y = 0
wall_normal: S
- BC: Neumann # Zero-gradient at top-north edge
order: 0
# Combined position: nodes at both z=N and y=N
pos: NF
# Wall normal points in the N direction for this combined edge
wall_normal: N
- BC: Neumann # Zero-gradient at top-south edge
order: 0
pos: SF
wall_normal: S
- BC: RegularizedHWBB # No-slip interior baffle defined by a predicate
order: 0
# `pos` also accepts a boolean predicate over node coordinates
# x, y, z (instead of a cardinal alias). This selects every node
# satisfying the expression - here an axis-aligned cuboid slab.
# Grammar: + - * / and integer powers (** or ^), comparisons
# (< <= > >= == !=) and boolean and/or/not (&, |, ~).
pos: "(x >= 14) & (x <= 17) & (8 <= y) & (y <= 24)"
# Required for non-cardinal predicate regions (cardinal-snapped).
wall_normal: E
# Optional: name the `domain.systems` frame `pos` is written in
# (default `domain.default_system`, itself "lbm"); each lattice node
# is mapped into the system and the position checked there (the
# transform - including rotation - is baked in at config load).
# Applies to a predicate and to a shape alike; not valid on a
# cardinal alias, which is an lbm-frame domain face by definition.
# system: site
- BC: RegularizedHWBB # No-slip interior wall placed by a typed shape
order: 0
# `pos` also accepts a typed shape instead of a string: it is lowered
# to the equivalent node predicate at config load, so the solver owns
# the thickness convention (a plane is one node layer thick, a line
# one node per step). Kinds and their fields:
# point -> pos
# points -> points
# line -> start, end
# plane -> axis, axis_pos, min, max
# box -> start, end, is_abs
# predicate -> predicate
pos:
kind: plane
# Plane's normal direction; the plane spans the other two axes.
axis: z
# Coordinate along `axis` where the plane sits.
axis_pos: 4.0
# Optional in-plane extent, ordered by global axis index excluding
# `axis` (axis=z -> (x, y)). Omit both to span the whole domain.
min: [10, 10]
max: [50, 50]
# Optional: rigid transform mapping the shape's local frame into
# the entry's coordinate system.
# transformation:
# rotation: [0, 0, 0.7854]
# A selection geometry takes no `dist`: spacing belongs to an
# export probe, which generates sample points.
# The frame lives on the entry, next to `pos`, in either spelling.
# system: site
wall_normal: B
# Reset mean density to rho_norm on the specified face after each step.
# Prevents slow pressure drift caused by mass conservation error at outlets.
# Empty list by default (no normalisation).
rho_normalization:
- pos: W # Face to compute the mean over. Options: N, S, W, E, F, B
rho_norm: 1.0 # Target mean density. Defaults to 1
# Inlet turbulence generator - produces turbulent inflow at the inlet (x = 0).
# Selected via `type`: `sem` (Synthetic Eddy Method, below) or `podfs`
# (Proper Orthogonal Decomposition Fourier Series, commented alternative
# further down). Requires the inlet face (W) to NOT have a UniformFlow BC.
inlet_turbulence:
type: sem
eddies:
# Integral length scale used for eddy size [x, y, z] in lattice units.
# Larger values produce bigger, lower-frequency structures.
lengthscale: {x: 14, y: 14, z: 14}
# Volumetric eddy density: n_eddies = density Γ (SEM_volume / eddy_volume).
# Higher values improve turbulence isotropy at the cost of more compute.
eddies_vol_density: 10
# Seed for the random number generator that initialises eddy positions.
# 0 = non-reproducible; any other integer gives a reproducible field.
seed_rand: 0
# Bounding box [y, z] for the eddy generation volume at the inlet.
# x extent is set automatically to 2 Γ lengthscale.x.
domain_limits_yz:
start: [16, 0] # [y_min, z_min] in lattice units
end: [48, 96] # [y_max, z_max] in lattice units
profile:
# CSV file containing the mean velocity and Reynolds stress tensor
# profile as a function of height z.
csv_profile_data: "fixture/SEM/example/real_profile.csv"
# Constant offset added to all z values when reading the profile.
# Shifts the profile vertically relative to the domain.
z_offset: 0
# Turbulence intensity tuning constant K.
# 1.0 matches the target turbulence intensity exactly.
K: 1
# Scale factor applied to all z (height) coordinates in the profile CSV.
# Use to convert from metres to lattice units. The profile is a
# function of height z only (the inlet is homogeneous in y).
# Defaults to 1
length_mul: 1
# Scale factor applied to all velocity values in the profile CSV.
# Reynolds stresses are scaled by vel_mul^2.
# Defaults to 1
vel_mul: 1
# PODFS inlet alternative (data-driven). Replaces the `inlet_turbulence`
# block above; only one inlet generator may be active at a time. PODFS
# replays a compact temporal basis through a Fourier series. The runtime is
# LOAD-ONLY: it consumes a single, self-describing basis file produced by a
# separate OFFLINE step (`nassu podfs-build <build_spec.yaml>`), which runs
# the plane-sampling -> POD transform and bakes the mean, modes, Fourier
# coefficients, analysis-grid coordinates, and an optional high-frequency
# supplement into the file. Theory: theory/BC/bc.inlet.
#
# inlet_turbulence:
# type: podfs
# # The prebuilt basis file (HDF5, written by `nassu podfs-build`). The
# # mean profile, POD modes, Fourier coefficients and the analysis grid all
# # live inside it; the solver makes no POD decisions at runtime.
# basis_path: "fixture/PODFS/example/basis.h5"
# vel_mul: 1.0 # whole-velocity rescale (intensity ratio preserved)
# time_rescale: 1.0 # maps basis time onto solver clock; T_eff = T/time_rescale
# high_freq: true # use the stored high-frequency supplement if present
# # Load-time mode crop: by count (n_modes) OR by cumulative energy
# # fraction (energy_crop, 0-1); the two are mutually exclusive. n_harmonics
# # crops the Fourier harmonics. Omit to use everything stored.
# n_modes: 200 # keep first N energy-ranked modes ...
# # energy_crop: 0.95 # ... OR fewest modes reaching 95% energy
# n_harmonics: 64 # keep DC + first K Fourier harmonics
# # Affine spatial placement of the basis onto the inlet: y symmetric about
# # the centreline, z grounded; below z_offset the field is zero. Omit for
# # the native span, centred and grounded at the recorded height.
# spatial:
# y_center: 80.0 # span centreline (default: inlet mid-width)
# y_scale: 1.0 # spanwise stretch of the basis width about y_center
# z_offset: 5.01 # ground level (default: basis recorded z-bottom)
# z_scale: 1.0 # vertical stretch of the basis height from z_offset
# ----------------------------------------------------------------
# Scalar transport
# ----------------------------------------------------------------
# Each entry under `scalar_transports` adds one passive-scalar
# advection-diffusion LBM coupled to the fluid. The dictionary key
# (e.g. `temperature`) is used as the symbol-name scope for the
# scalar's macroscopics, so two scalars produce disjoint variables
# (`temperature_phi`, `pollutant_phi`, etc.).
scalar_transports:
temperature:
# Lattice for the scalar populations `g_i`. D3Q7 is the minimal
# production choice; D2Q5 is the 2-D debug analogue. Larger sets
# (D3Q15 / D3Q19 / D3Q27) are accepted at higher memory cost.
velocity_set: D3Q7
# Only RRBGK is supported today; the field is fixed for forward
# compatibility with future operators (e.g. TRT).
collision_operator: RRBGK
# When to advance the scalar. Defaults to (0, 0) which means
# the scalar is not stepped; set start_step / end_step to enable.
interval: {start_step: 0, end_step: 0}
adv_diff_equation:
# Molecular diffusivity D in lattice units. The LES path adds
# nu_SGS / Sc_t to this when LES is active.
D: 1.0e-5
# Source / sink term S(x, y, z, t, phi). Use "0" for a passive
# scalar with no source.
S: "0"
# Turbulent Schmidt (Prandtl) number: with LES active, the eddy
# diffusivity nu_SGS / Sc_t adds to D node-locally (collision and
# multiblock level transfers). Runtime tunable; ignored without LES.
Sc_t: 0.7
# By default a passive transported field that goes NaN/Inf is frozen
# and dropped from exports while the fluid keeps running; a coupled
# field aborts the run. To instead abort on THIS field's
# divergence, declare a `data.guards` entry that overrides its default,
# e.g. `{field: my-scalar_phi, condition: is_nonfinite, action: stop}`.
# Optional Boussinesq coupling: this scalar exerts the body force
# F_buoy = -rho0 * beta * (phi - phi_ref) * gravity on the fluid
# (Guo forcing). `gravity` points along gravity in lattice units
# (e.g. [0, 0, -g_lbm] with z up), so phi > phi_ref rises. The
# parameters are compile-time; at most one scalar may be buoyant.
# Costs one float per node (signed magnitude along gravity).
buoyancy:
beta: 2.0e-3
phi_ref: 0.0
gravity: [0.0, 0.0, -1.0e-5]
rho0: 1.0
# Optional startup ramp: linearly ease the buoyancy force in from
# zero over this many coarsest-level steps, removing the step-0
# buoyancy shock. 0 (default) applies it at full strength.
ramp_steps: 0
# Initial field expression; evaluated once at setup. May reference
# (x, y, z) in lattice coordinates.
initial_field: "0"
# Per-scalar boundary conditions. Aliases live in
# `AllScalarBCSchemesAliases` (ScalarRegularizedDirichlet,
# ScalarRegularizedNeumann, ScalarRegularizedRobin,
# ScalarUniformInlet). Periodicity is
# inherited from `models.BC.periodic_dims`.
BC:
BC_map:
# Zero-flux (adiabatic) wall on the south face (regularized
# Neumann with zero prescribed flux).
- pos: S
BC: ScalarRegularizedNeumann
params:
J_w: 0.0
wall_normal: S
# Prescribed flux on the north face.
- pos: N
BC: ScalarRegularizedNeumann
wall_normal: N
params:
J_w: 0.0
- pos: E
BC: ScalarRegularizedRobin
wall_normal: E
params:
h: 0.0058
phi_inf: 0.0
# Uniform inlet on the west face. `phi_inlet` accepts either a
# constant (e.g. `phi_inlet: 1.0`) or an (x, y, z) equation string
# for a height-varying (stratified) inlet (below).
# `ScalarRegularizedDirichlet.phi_w` accepts the same forms.
- pos: W
BC: ScalarUniformInlet
params:
phi_inlet: 0.0
# z-varying (stratified) form:
# phi_inlet: "0.05 * (z / 100.0) ** 0.25"
# `pos` also accepts a boolean predicate over node coordinates
# x, y, z, exactly as the fluid `BC_map` does. This places the
# scalar wall on an interior surface (here an emitting footprint
# on the ground) instead of a whole cardinal domain face.
# Grammar: + - * / and integer powers (** or ^), comparisons
# (< <= > >= == !=) and boolean and/or/not (&, |, ~).
- pos: "(z <= 0) & (x >= 120) & (x <= 180) & (y >= 40) & (y <= 90)"
BC: ScalarRegularizedDirichlet
# Required: the outward cardinal direction of the wall. It is
# never inferred from `pos`, for a cardinal alias or a predicate
# region alike; a missing one is rejected at config load.
wall_normal: B
order: 1
params:
phi_w: 1.0
# Optional: name the `domain.systems` frame `pos` is written
# in (default `domain.default_system`, itself "lbm"); each
# lattice node is mapped into the system and the position
# checked there (the transform, including rotation, is baked in
# at config load). Applies to a predicate and to a shape alike;
# not valid on a cardinal alias.
# system: site
# Volumetric scalar source (region emission): add a constant rate
# to the scalar collision on every node inside a region (a
# pollutant-emitting region), complementing the `adv_diff_equation.S`
# expression. The region is placed by the same `pos` a surface BC
# takes: a cardinal alias, a boolean predicate over x, y, z, or a
# typed shape. The `rate` is compile-time (baked into the kernel);
# all regions of one scalar must share the same `rate`, and the
# region geometry is shared across scalars (per-scalar rate).
source_regions:
- pos: "(z >= 30) & (z <= 38)"
rate: 1.0e-3
# Optional: a `domain.systems` frame `pos` is written in, baked
# into the resolved predicate at config load (rotations OK).
# system: site
# Double-distribution-function (DDF) energy field. A
# transported field of `kind: energy` rides the SAME advection-diffusion
# path as a scalar: it transports the conserved energy density
# `energy = rho h` (a Gaussian blob here), advected and diffused by the
# fluid velocity at the thermal diffusivity `alpha` (the energy analogue
# of the scalar `D`) with turbulent Prandtl number `Pr_t` (the analogue
# of `Sc_t`). The energy source is the intrinsic deviation-cancelling
# correction (no user `adv_diff_equation`/`buoyancy`/`source_regions`).
# The energy set must be rank-4 isotropic (D2Q9/D3Q15/D3Q19/D3Q27);
# D2Q5/D3Q7 are rejected. See the DDF energy theory page.
energy:
kind: energy
velocity_set: D3Q27
collision_operator: RRBGK
interval: {start_step: 0, end_step: 0}
# Thermal diffusivity mu/(rho_0 Pr) (constant), in the slot the
# scalar uses for `D`.
alpha: 1.0e-3
# Turbulent Prandtl number, in the slot the scalar uses for `Sc_t`.
# Consumed only when LES is active; runtime tunable.
Pr_t: 0.7
# Initial energy density field; may reference (x, y, z) in lattice
# coordinates.
initial_field: "exp(-((x-8)*(x-8) + (y-8)*(y-8) + (z-8)*(z-8)) / 8.0)"
# ----------------------------------------------------------------
# Volumetric (region) boundary conditions
# ----------------------------------------------------------------
# Unlike surface BCs, a volumetric region acts on every fluid node inside a
# region of the domain. Each region is placed by the same `pos` a surface
# BC takes: a cardinal alias, a predicate over x, y, z, or a shape. Two
# coefficients combine on region nodes, both baked into the kernel as Guo
# body forces:
# porous_alpha -> linear Darcy sink F[a] = -alpha * u[a]
# porous_beta -> quadratic Forchheimer/canopy drag F[a] = -beta * |u| * u[a]
# All regions share one alpha and one beta (the same value must be repeated
# on every region); both coefficients are level-0 lattice values, rescaled
# by 1/2^lvl per refinement level.
volumetric_regions:
# Linear Darcy region: an outlet sponge that damps pressure waves and
# near-outlet velocity proportionally to the local speed (beta omitted -> 0).
- pos: "(x >= 480) & (x <= 500)"
porous_alpha: 0.2
porous_beta: 0.05
# Quadratic canopy region: a forest/canopy momentum sink whose drag grows
# with |u|^2 (Forchheimer / pressure-decay). Shares the single alpha/beta.
- pos: "(z >= 0) & (z <= 20) & (x >= 100) & (x <= 200)"
porous_alpha: 0.2
porous_beta: 0.05
# Optional: a `domain.systems` frame the predicate is written in.
# Baked into `pos` at config load; rotations supported.
# system: site
# ----------------------------------------------------------------
# Variable-density low-Mach thermal closure (Taha et al. 2024)
# ----------------------------------------------------------------
# Opt-in Tier 3 of the thermal modelling hierarchy: the density is
# slaved to the ideal-gas equation of state rho = P / (r T) instead of
# the population sum, and buoyancy is the exact (rho - rho_inf) * gravity
# term (the Boussinesq force is its linearised small-dT limit). Unset
# this block to keep the isothermal / Boussinesq path bit-identical.
# D3Q27 only; mutually exclusive with the legacy LBM.thermal_model flag.
low_mach:
# Specific gas constant in lattice units, closing rho = P / (r T).
r: 1.0
# Spatially uniform thermodynamic pressure (lattice units). For an
# open domain dP/dt = 0 and it stays at this ambient value.
P_thermo: 1.0
# Reference / ambient temperature (lattice units), used by the EOS
# and as the buoyancy reference state.
T_ref: 1.0
# Ambient density in the buoyancy term (rho - rho_inf) * gravity.
# Omit to default to the EOS-consistent value P / (r T_ref).
rho_inf: 1.0
# Molecular Prandtl number: thermal conductivity lambda = cp mu(T) / Pr.
Pr: 0.7
# Constant specific heat at constant pressure (lattice units). The energy
# field transports specific enthalpy h and derives T = T_ref + h/cp.
cp: 1.0
# Reference dynamic viscosity mu_0 of the law mu(T) = mu_0 (T/T_ref)^n.
# Omit to default to the molecular lattice value nu_0 = (tau - 1/2)/3.
mu_ref: 0.05
# Power-law exponent n of mu(T) = mu_0 (T/T_ref)^n. 0 (default) is a
# constant viscosity; the effective diffusivity alpha_eff = mu/(rho Pr)
# still varies through the EOS density.
mu_exponent: 0.0
# Gravitational acceleration vector (lattice units), along gravity.
gravity: [0.0, 0.0, -1.0e-5]
# Thermodynamic-pressure closure. 'open' pins P to ambient
# (dP/dt = 0); valid here because the domain vents via the W inlet and the
# E RegularizedNeumannOutlet on the fluid BC_map above. 'closed' instead
# evolves P(t) = M r / integral(1/T dV) for a sealed (no inlet/outlet),
# heated enclosure - a heated wall-sealed domain must use 'closed'.
domain_closure: open
# Finite-difference energy field: transports specific
# enthalpy h per rho Dh/Dt = div(lambda grad T) + Q, deriving the
# temperature T = T_ref + h/cp. Conduction uses the per-node diffusivity
# alpha_eff = mu(T)/(rho Pr) (variable-lambda face-flux form); advection
# u.grad h is upwind. Omit the whole block to default to a uniform T_ref
# start with no heat source.
energy:
# Initial temperature field T(x, y, z) in lattice units; the enthalpy
# is seeded as h = cp (T - T_ref). A uniform ambient start is just the
# constant T_ref.
initial_field: "1.0"
# Volumetric heat sources (a burner): a constant volumetric heat-release
# rate Q (energy per unit volume per unit time, lattice units) on nodes
# inside each predicate region; Q enters rho Dh/Dt (added as Q/rho). The
# kernel bakes one rate, so every region must share the rate.
source_regions:
- pos: "(x >= 6) & (x <= 10) & (y >= 6) & (y <= 10)"
rate: 1.0e-3
# Temperature wall BCs on the domain faces of the FD energy field.
# One entry per cardinal face, same W/E/S/N/B/F
# convention as the fluid BCs (W=x-min, E=x-max, S=y-min, N=y-max,
# B=z-min, F=z-max). A face with no entry keeps the adiabatic ghost
# fallback; a periodic axis (models.BC.periodic_dims) may not carry a
# wall BC. Aliases: TempDirichlet (fixed T_w), TempNeumann (fixed flux
# J_w, default 0.0 = adiabatic), TempRobin (convective h, T_inf).
wall_bcs:
- pos: "W" # x-min wall held at a fixed temperature
BC: TempDirichlet
T_w: 2.0
- pos: "E" # x-max convective (Robin) wall
BC: TempRobin
h: 0.1
T_inf: 1.0
# Tier 2 weakly-compressible thermal coupling. The shared
# finite-difference energy field feeds a temperature deviation
# theta = T / T_ref - 1 into the equilibrium so p = rho cs^2 (1 + theta);
# the density stays rho = sum_i f_i (unlike the Tier 3 low_mach closure).
# Shown commented because it requires LBM.thermal_model: true and is
# MUTUALLY EXCLUSIVE with the low_mach block active above.
# energy:
# # Reference temperature for the deviation rescale theta = T/T_ref - 1.
# T_ref: 1.0
# # Molecular Prandtl number: thermal conductivity lambda = cp mu(T) / Pr.
# Pr: 0.7
# # Constant specific heat (caloric relation T = T_ref + h/cp).
# cp: 1.0
# # Reference dynamic viscosity mu_0 (omit -> nu_0 = (tau - 1/2)/3) and
# # power-law exponent n of mu(T) = mu_0 (T/T_ref)^n.
# mu_ref: 0.05
# mu_exponent: 0.0
# # The temperature field itself - the SAME EnergyFieldConfig the
# # low_mach closure uses (initial condition, volumetric heat sources,
# # temperature wall BCs). Omit to default to a uniform T_ref start.
# field:
# initial_field: "1.0"
# wall_bcs:
# - pos: "W"
# BC: TempDirichlet
# T_w: 2.0
# - pos: "E"
# BC: TempDirichlet
# T_w: 1.0
# ----------------------------------------------------------------
# Debug
# ----------------------------------------------------------------
# Developer flags - leave all values at their defaults for production runs.
debug:
IBM: {no_force_spread: false, no_nodes_export: false}
# no_force_spread: true skips IBM force spreading (diagnostics only)
# no_nodes_export: true suppresses IBM node export even if configured
LBM: {collision_only: false, no_macrs_export: false, streaming_only: false}
# collision_only: true runs only the collision step (no streaming)
# no_macrs_export: true suppresses all macroscopic field output
# streaming_only: true runs only the streaming step (no collision)
code_generation: {load_generated_files: false, save_generated_files: false}
# load_generated_files: true reuses previously generated CUDA source files
# instead of regenerating - never use in production
# save_generated_files: true writes the generated CUDA source and compile log
# to the run's code_generated/ folder; off by default so a normal run never
# writes to disk (works on read-only package installs) - diagnostics only
multiblock: {export_comm_vtk: false, run_communication: true}
# export_comm_vtk: true writes block-communication debug VTK files
# run_communication: false skips multiblock communication (diagnostics)
output_IBM_nodes: false # True to print IBM node data to stdout
output_only: false # True to run export routines without advancing the LBM
profile: false # True to enable CUDA profiling markers
profile_kernels: false # True to record per-kernel GPU timings (CUDA
# events) and the host-overhead metric into info.yaml
isolate: # Run only one subsystem's kernels in the step loop (solo
# mode for performance measurement): LBM, comm, IBM, BC, scalar, SEM or
# proc. Setup and the init kernel sequence always run full.