Turbulent Channel with Passive Scalar - Kawamura DNS (Case 02)¶
This notebook validates the scalar advection-diffusion (DDF) module under fully-developed wall-bounded turbulence. The fluid is the periodic plane channel of {ref}vc_turb_channel_flow at \(\mathrm{Re}_\tau \approx 180\); a passive scalar \(\phi\) is transported on top of it with Dirichlet walls at \(\phi_w = 0\) (south, \(y = 0\)) and \(\phi_w = 1\) (north, \(y = L_y\)). With \(\mathrm{Pr} = \nu / D = 0.71\) this is the canonical heated-channel benchmark and the
first turbulent exercise of the scalar regularised wall BC.
The validation targets are the three Reynolds-averaged scalar profiles, in wall units, compared against the Kawamura DNS database:
mean scalar profile \(\langle \phi \rangle^+(y^+)\);
scalar fluctuation RMS \(\langle \phi'^2 \rangle^{1/2,+}(y^+)\);
turbulent scalar fluxes \(\langle u'\phi' \rangle^+(y^+)\) and \(\langle v'\phi' \rangle^+(y^+)\).
All three come from a single source: the channel_profile.mid_channel plane time series, averaged over the homogeneous streamwise direction and the statistics window (see below).
Setup¶
[1]:
import pathlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import nassu.viz as common
from nassu.cfg.model import ConfigScheme
common.use_style()
Load simulation configuration¶
[2]:
CASE_DIR = pathlib.Path("validation/scalar_transport/02_turb_channel_passive_scalar")
filename = str(CASE_DIR / "02_turb_channel_passive_scalar.nassu.yaml")
sim_cfgs = ConfigScheme.sim_cfgs_from_file_dct(filename)
sim_cfg = next(
cfg for (name, _), cfg in sim_cfgs.items() if cfg.name == "turbulentChannelPassiveScalar"
)
ds = sim_cfg.domain.domain_size
Ly = ds.y
delta = Ly / 2.0 # channel half-height (lattice units)
# Physical scales (lattice units). u* is fixed by the driving body force,
# F_x = u*^2 / delta -> u* = sqrt(F_x * delta).
nu = sim_cfg.models.LBM.kinematic_viscosity
F_x = sim_cfg.models.LBM.F.x
u_star = float(np.sqrt(F_x * delta))
re_tau = u_star * delta / nu
scalar_cfg = sim_cfg.models.scalar_transports["scalar"]
D = scalar_cfg.adv_diff_equation.D
Pr = nu / D
SCALAR_NAME = "scalar" # name of the transported scalar field
PHI_KEY = f"{SCALAR_NAME}_phi"
print(f"domain (x,y,z) = ({ds.x}, {ds.y}, {ds.z})")
print(f"delta = {delta:.1f}")
print(f"nu = {nu:.6e}")
print(f"u* = {u_star:.6e}")
print(f"Re_tau = {re_tau:.1f}")
print(f"D = {D:.6e}")
print(f"Pr = nu / D = {Pr:.4f}")
domain (x,y,z) = (360, 120, 192)
delta = 60.0
nu = 1.666667e-03
u* = 5.000020e-03
Re_tau = 180.0
D = 2.347500e-03
Pr = nu / D = 0.7100
Wall units and reference correlations¶
Wall-normal distance in viscous units is \(y^+ = y\, u^* / \nu\). The scalar is normalised by the friction scalar \(\phi^* = q_w / u^*\) (with \(\rho c_p = 1\) in lattice units), where the wall flux \(q_w = D\, \mathrm{d}\langle\phi\rangle / \mathrm{d}y\) is read from the near-wall gradient of the mean profile. The normalised scalar is then \(\phi^+ = (\phi - \phi_w) / \phi^*\), measured from each wall.
In place of digitised DNS points, two analytical references frame the comparison:
Viscous sublayer: \(\langle\phi\rangle^+ = \mathrm{Pr}\, y^+\), exact as \(y^+ \to 0\).
Log layer: the Kader (1981) correlation \(\langle\phi\rangle^+ = 2.12\,\ln(y^+) + \beta(\mathrm{Pr})\) with \(\beta(\mathrm{Pr}) = \big(3.85\,\mathrm{Pr}^{1/3} - 1.3\big)^2 + 2.12\,\ln(\mathrm{Pr})\).
Digitised Kawamura \(\mathrm{Re}_\tau = 180\), \(\mathrm{Pr} = 0.71\) profiles, if present as CSV files under reference/, are overlaid automatically (see the loader below).
[3]:
def phi_sublayer(y_plus):
# Viscous-sublayer asymptote phi+ = Pr y+.
return Pr * np.asarray(y_plus)
def phi_kader_loglaw(y_plus, pr=Pr):
# Kader (1981) thermal log law: phi+ = 2.12 ln(y+) + beta(Pr).
beta = (3.85 * pr ** (1.0 / 3.0) - 1.3) ** 2 + 2.12 * np.log(pr)
return 2.12 * np.log(np.asarray(y_plus)) + beta
def load_reference_profiles(ref_dir):
# Load Kawamura DNS CSVs from `reference/` if present. Expected files (each
# with a `y+` column plus the named value column):
# phi_mean.csv -> columns: y+, phi+
# phi_rms.csv -> columns: y+, phi_rms+
# uphi_flux.csv -> columns: y+, u'phi'+
# vphi_flux.csv -> columns: y+, v'phi'+
# Missing files are silently skipped.
wanted = {
"phi_mean": "phi_mean.csv",
"phi_rms": "phi_rms.csv",
"uphi_flux": "uphi_flux.csv",
"vphi_flux": "vphi_flux.csv",
}
out = {}
for key, fname in wanted.items():
path = ref_dir / fname
if path.exists():
out[key] = pd.read_csv(path, comment="#")
return out
reference = load_reference_profiles(CASE_DIR / "reference")
print("reference profiles found:", sorted(reference.keys()) or "none")
reference profiles found: ['phi_mean', 'phi_rms', 'uphi_flux', 'vphi_flux']
Scalar statistics from the mid-channel plane time series¶
Every Reynolds-averaged scalar profile is reconstructed from one source: the channel_profile.mid_channel plane probe. The plane is normal to the spanwise axis \(z\) and spans the full streamwise / wall-normal \((x, y)\) domain, sampled every 100 steps through the statistics window. The flow is homogeneous in \(x\), so for each wall-normal node \(y\) we treat every \((x, t)\) pair as an independent sample and average over them:
mean \(\langle\phi\rangle(y)\) and RMS \(\langle\phi'^2\rangle^{1/2}(y) = \sqrt{\langle\phi^2\rangle - \langle\phi\rangle^2}\);
turbulent fluxes \(\langle u'\phi'\rangle(y)\) and \(\langle v'\phi'\rangle(y)\), formed as the covariances \(\overline{u_\alpha\,\phi} - \overline{u_\alpha}\,\overline{\phi}\) over the same samples.
A single plane therefore yields the cross-correlation fluxes that an auto-variance statistics kernel cannot.
[4]:
series_cfg = sim_cfg.data.exports["channel_profile"]
STATS_START = series_cfg.interval.start_step
plane_probe = sim_cfg.output.exports["channel_profile"].series.planes["mid_channel"]
def plane_wallnormal_columns():
# Group the plane's sample points by wall-normal node y. Streamwise x is the
# homogeneous direction we average over, so each y maps to the list of
# point-index columns at that height.
pts = pd.read_csv(plane_probe.points_filename)
y_levels = np.sort(pts["y"].unique())
cols_by_y = [[str(int(i)) for i in pts.loc[pts["y"] == yv, "idx"]] for yv in y_levels]
return y_levels, cols_by_y
def gather_samples(macr_name, cols_by_y):
# Stack every (streamwise x, time) sample per wall-normal node over the
# statistics window. Returns an array of shape (n_y, n_samples).
df = plane_probe.read_full_data(macr_name)
df = df.loc[df["time_step"].to_numpy() >= STATS_START]
return np.array([df[cols].to_numpy().ravel() for cols in cols_by_y])
y_pl, cols_by_y = plane_wallnormal_columns()
phi_s = gather_samples(PHI_KEY, cols_by_y)
ux_s = gather_samples("ux", cols_by_y)
uy_s = gather_samples("uy", cols_by_y)
phi_mean = np.nanmean(phi_s, axis=1)
phi_var = np.clip(np.nanmean(phi_s**2, axis=1) - phi_mean**2, 0.0, None)
phi_rms = np.sqrt(phi_var)
# Turbulent scalar fluxes as covariances over the same (x, time) samples.
uphi = np.nanmean(ux_s * phi_s, axis=1) - np.nanmean(ux_s, axis=1) * phi_mean
vphi = np.nanmean(uy_s * phi_s, axis=1) - np.nanmean(uy_s, axis=1) * phi_mean
print(f"plane stats over {phi_s.shape[1]} (x, time) samples per wall-normal node")
plane stats over 540360 (x, time) samples per wall-normal node
Friction scalar \(\phi^*\) and wall normalisation¶
The wall flux is estimated from the near-wall slope of \(\langle\phi\rangle\) at each wall (a one-sided linear fit over the first few lattice nodes). The two walls are antisymmetric (\(\phi_w = 0\) vs \(\phi_w = 1\)), so each profile is folded onto \(0 \le y^+ \le \mathrm{Re}_\tau\) measured from the nearest wall.
Folding uses the parity of each quantity under the channel reflection \(y \to L_y - y\) combined with \(\phi \to 1 - \phi\) (a symmetry of the time-averaged statistics): \(\langle\phi\rangle\) and \(\langle\phi'^2\rangle\) are even, the wall-normal flux \(\langle v'\phi'\rangle\) is even, and the streamwise flux \(\langle u'\phi'\rangle\) is odd.
[5]:
def fold_to_south(prof, sign_north=1.0):
# Fold a full-channel wall-normal profile onto the south half, distances
# measured from the nearest wall. The north branch is interpolated onto the
# south-wall y+ grid; sign_north is +1 for quantities even under the channel
# reflection y -> Ly - y and -1 for odd ones.
yp_s = y_pl * u_star / nu
yp_n = (Ly - y_pl) * u_star / nu
half = len(y_pl) // 2
prof_n_on_s = np.interp(yp_s, yp_n[::-1], (sign_north * prof)[::-1])
return yp_s[:half], (0.5 * (prof + prof_n_on_s))[:half]
# Wall scalar flux q_w = D |dphi/dy| from a one-sided near-wall linear fit
# at each wall; the friction scalar is phi* = q_w / u* (rho c_p = 1).
south_slope = np.polyfit(y_pl[1:5], phi_mean[1:5], 1)[0]
north_slope = np.polyfit(y_pl[-4:], phi_mean[-4:], 1)[0]
q_w = 0.5 * D * (abs(south_slope) + abs(north_slope))
phi_star = q_w / u_star
# Mean measured from each wall (south: phi - 0, north: 1 - phi), folded.
half = len(y_pl) // 2
yp = (y_pl * u_star / nu)[:half]
yp_n = (Ly - y_pl) * u_star / nu
phip_south = (phi_mean - 0.0) / phi_star
phip_north = (1.0 - phi_mean) / phi_star
phip_north_on_s = np.interp(y_pl * u_star / nu, yp_n[::-1], phip_north[::-1])
phip_mean = (0.5 * (phip_south + phip_north_on_s))[:half]
# RMS (even); fluxes: <u'phi'> odd, <v'phi'> even.
_, phirms_fold = fold_to_south(phi_rms, sign_north=1.0)
phirms_plus = phirms_fold / phi_star
_, uphi_fold = fold_to_south(uphi, sign_north=-1.0)
_, vphi_fold = fold_to_south(vphi, sign_north=1.0)
uphi_plus = uphi_fold / (u_star * phi_star)
vphi_plus = vphi_fold / (u_star * phi_star)
print(f"q_w = {q_w:.6e}, phi* = {phi_star:.6e}")
q_w = 9.207393e-05, phi* = 1.841471e-02
[6]:
fig, ax = common.fig_single()
yp_ref = np.logspace(-1, np.log10(re_tau), 200)
# The Pr y+ asymptote only holds in the viscous sublayer; clip it to the
# near-wall region so it does not dominate the y-axis far from the wall.
mask_sub = yp_ref <= 12.0
ax.plot(
yp_ref[mask_sub],
phi_sublayer(yp_ref[mask_sub]),
**common.markers.exp_line(color=common.colors.blue, linestyle=":"),
label=r"$\mathrm{Pr}\,y^+$ (sublayer)",
)
mask_log = yp_ref > 5
ax.plot(
yp_ref[mask_log],
phi_kader_loglaw(yp_ref[mask_log]),
**common.markers.exp_line(linestyle="--"),
label="Kader (1981) log law",
)
ax.plot(
yp[1:],
phip_mean[1:],
**common.markers.sim_line(linestyle="-"),
label="AeroSim D3Q27 RR-BGK",
)
if "phi_mean" in reference:
df = reference["phi_mean"]
ax.plot(df["y+"], df["phi+"], **common.markers.exp(shape="o"), label="Kawamura DNS")
ax.set_xscale("symlog")
ax.set_xlim(0.3, re_tau)
ax.set_ylim(0, None)
ax.set_xlabel(r"$y^+$")
ax.set_ylabel(r"$\langle \phi \rangle^+$")
ax.set_title(r"Mean scalar profile ($\mathrm{Re}_\tau \approx 180$, $\mathrm{Pr} = 0.71$)")
ax.legend()
plt.tight_layout()
plt.show()
[7]:
fig, ax = common.fig_single()
ax.plot(
yp[1:],
phirms_plus[1:],
**common.markers.sim_line(linestyle="-"),
label="AeroSim D3Q27 RR-BGK",
)
if "phi_rms" in reference:
df = reference["phi_rms"]
ax.plot(df["y+"], df["phi_rms+"], **common.markers.exp(shape="o"), label="Kawamura DNS")
ax.set_xlim(0, re_tau)
ax.set_xlabel(r"$y^+$")
ax.set_ylabel(r"$\langle \phi'^2 \rangle^{1/2,+}$")
ax.set_title("Scalar RMS fluctuation profile")
ax.legend()
plt.tight_layout()
plt.show()
[8]:
fig, ax = common.fig_single()
# Each flux is a distinct quantity, so each gets its own colour and marker;
# AeroSim is the solid line, the DNS overlay the matching hollow markers.
flux_plot = (
(uphi_plus, r"$\langle u'\phi' \rangle^+$", "uphi_flux", "u'phi'+", common.colors.sim, "o"),
(vphi_plus, r"$\langle v'\phi' \rangle^+$", "vphi_flux", "v'phi'+", common.colors.blue, "s"),
)
common.refline(ax, 0.0)
for cov_plus, lbl, ref_key, ref_col, color, shape in flux_plot:
ax.plot(
yp,
cov_plus,
**common.markers.sim_line(color=color, linestyle="-"),
label=f"AeroSim {lbl}",
)
if ref_key in reference:
df = reference[ref_key]
ax.plot(
df["y+"],
df[ref_col],
**common.markers.exp(shape=shape, color=color),
label=f"Kawamura {lbl}",
)
ax.set_xlim(0, re_tau)
ax.set_xlabel(r"$y^+$")
ax.set_ylabel(r"$\langle u_\alpha'\phi' \rangle^+$")
ax.set_title("Turbulent scalar flux profiles")
ax.legend()
plt.tight_layout()
plt.show()
Flow field¶
Instantaneous scalar field on the streamwise / wall-normal mid-plane (channel_profile.mid_channel) at the last exported step, showing \(\phi\) between the two Dirichlet walls (\(\phi = 0\) at the bottom, \(\phi = 1\) at the top).
[9]:
from nassu import viz
viz.enable_offscreen()
PANEL = (1040, 360)
source = viz.PlaneSource.from_cfg(sim_cfg, series="channel_profile", plane="mid_channel")
view = viz.frame_domain(
(float(ds.x), float(ds.y), float(ds.z)),
"z",
panel=PANEL,
slice_coord=float(ds.z) / 2.0,
)
plotter = viz.render_grid(
[viz.Panel("mid-channel", source, view)],
steps=[source.steps[-1]],
scalar=PHI_KEY,
cmap="coolwarm",
clim=(0.0, 1.0),
bar_title="phi",
panel_size=PANEL,
)
plotter.show()
2026-07-23 02:54:41.186 ( 29.708s) [ 7F415D968B80]vtkXOpenGLRenderWindow.:1460 WARN| bad X server connection. DISPLAY=
/tmp/ipykernel_2107549/505376266.py:23: UserWarning: Using static image for notebook display.
Install trame for interactive backends: pip install "pyvista[jupyter]"
plotter.show()
Summary¶
A passing result reproduces, in wall units:
the linear viscous sublayer \(\langle\phi\rangle^+ = \mathrm{Pr}\,y^+\) close to the wall;
the slope and offset of the scalar log layer (Kader correlation / Kawamura DNS);
the position and amplitude of the scalar RMS peak in the buffer layer;
the turbulent scalar flux \(\langle v'\phi'\rangle^+\) across the channel core within a few percent of the Kawamura DNS.
Version¶
[10]:
sim_info = sim_cfg.output.read_info()
print("Version:", sim_info["version"])
print("Commit hash:", sim_info["commit"])
Version: 2.0.0a7
Commit hash: 5e47f2762575c2d285254d36bfd354b6af09fda1
Configuration¶
[11]:
from IPython.display import Code
Code(filename=filename)
[11]:
# Turbulent channel flow with passive scalar - Kawamura DNS validation
#
# Validates the scalar advection-diffusion module under fully-developed
# wall-bounded turbulence: plane channel at Re_tau ~= 180 with a passive
# scalar held at phi = 0 / phi = 1 on the two walls, against the
# Kawamura et al. (1998, 1999) DNS database at Pr = 0.71.
#
# Resolution (issue #874): the scalar is resolved at delta = 60 lattice units
# (dx+ = 3.0 uniformly), 1.5x the earlier delta = 40. The earlier coarse grid
# put the core cell-Peclet at Pe_cell = u_local dx / D ~= 57, where the
# advection-dominated AD-LBM scalar developed a growing streamwise grid-scale
# checkerboard in the channel core (the LES SGS diffusivity cannot damp it
# there - Smagorinsky nu_SGS -> 0 at the low-strain centreline) and diverged.
# Refining to delta = 60 lowers the core cell-Peclet to ~38, which a controlled
# diffusivity sweep confirmed is on the stable side, while keeping Pr = 0.71
# exact (finer dx -> larger nu, D in lattice units). See
# examples/debug_scalar_874/ for the diagnosis and the stability sweep.
#
# The box is 6 delta x 2 delta x 3.2 delta = 360 x 120 x 192, matching
# Kawamura's 6.4 x 2 x 3.2 aspect ratio, with Lx+ = 1080 and Lz+ = 576 - well
# above the minimal-flow-unit thresholds (Lx+ ~ 350, Lz+ ~ 100), so the
# buffer-layer streak dynamics and the scalar statistics are not constrained by
# the box. At dx+ = 3.0 the wall is wall-resolved for the LES, so the grid is
# uniform (no multiblock refinement, hence no level-transfer interface).
#
# - Re_tau = 180: u* = 0.0050, nu = u* delta / Re_tau = 1.6667e-3
# (tau = 0.505), driving force F_x = u*^2 / delta = 4.1667e-7.
# - One eddy turnover time ETT = delta / u* = 12000 steps.
# n_steps = 225000 ~= 19 ETT: the fluid is cold-started from an
# analytic Reichardt mean profile perturbed with wall-enveloped
# multi-mode sinusoids (see models.initialization.equations) that
# trip transition to turbulence. Statistics start after ~6 ETT,
# once the flow is fully developed, and average over ~12.5 ETT.
#
# Passive scalar:
#
# - D3Q27 + RRBGK collision (same set as the fluid), for its isotropy at
# the advection-dominated cell Peclet.
# - Dirichlet walls: phi = 0 at the south (y = 0) wall, phi = 1 at
# the north (y = 120) wall (`ScalarRegularizedDirichlet`).
# - Periodic in x and z (matches the fluid topology).
# - Molecular Prandtl number Pr = nu / D = 0.71 (air, the most
# referenced Kawamura curve): D = nu / Pr = 2.3475e-3 in lattice
# units; with cs2_phi = 1/3 this gives tau_phi = 0.50704.
# - Wall-resolved LES (Smagorinsky, Cs = 0.17) supplies the turbulent
# scalar diffusivity D_total = D + nu_SGS / Sc_t (Sc_t = 0.7) away from
# the wall; the centreline stability is carried by the resolution
# (core Pe_cell ~= 38), not the SGS closure.
# - Initial scalar field: y / 120, the laminar steady-state linear
# ramp. Lets the scalar statistics converge faster than starting
# from phi = 0 everywhere.
#
# Reference: Kawamura et al. (1998, 1999) DNS database for the
# turbulent passive scalar in a plane channel. Validation profiles:
#
# <phi>+(y+) mean scalar profile in wall units
# <phi'^2>+(y+) RMS scalar fluctuation profile
# <u'phi'>+(y+) turbulent scalar flux profile
#
# The companion notebook reconstructs all of these offline from the
# `channel_profile.mid_channel` plane time series (streamwise + time
# averaging).
simulations:
- name: turbulentChannelPassiveScalar
save_path: ./validation/scalar_transport/02_turb_channel_passive_scalar/results/
n_steps: 225000
report:
frequency: 2000
domain:
domain_size:
x: 360
y: 120
z: 192
block_size: 8
data:
# Field monitors to watch the scalar develop and catch a divergence
# early. min/max/mean of phi over the whole domain track its envelope;
# the running-maximum location (`pos`) tells a wall-BC blow-up apart
# from a bulk one; rho is a fluid-health sanity check.
monitors:
fields:
scalar_phi:
macrs: [scalar_phi]
stats: [min, max, mean, pos]
interval: {frequency: 100}
rho:
macrs: [rho]
stats: [min, max, mean, pos]
interval: {frequency: 100}
exports:
default:
macrs: [rho, u, scalar_phi]
interval:
frequency: 225000
lvl: 0
target:
volumes:
default: {}
outputs:
instantaneous: true
channel_profile:
macrs: [rho, u, scalar_phi]
interval:
start_step: 75000
end_step: 225000
frequency: 100
lvl: 0
target:
planes:
# Streamwise / wall-normal plane (walls are y-normal) at the
# spanwise mid-point. Spans the full (x, y) domain: the source
# for every scalar statistic and for the flow-field view.
mid_channel:
axis: z
axis_pos: 96
dist: 1
outputs:
instantaneous: true
models:
precision:
default: single
LBM:
tau: 0.505
F:
x: 4.1667e-7
y: 0
z: 0
vel_set: D3Q27
coll_oper: RRBGK
# Wall-resolved LES. The Smagorinsky SGS viscosity also feeds the
# scalar turbulent diffusivity (D_total = D + nu_SGS / Sc_t) away from
# the wall; centreline scalar stability is carried by the resolution.
LES:
model: Smagorinsky
sgs_cte: 0.17
initialization:
# Equation-based ("cold") start - self-contained, no external
# artefact. The streamwise field is an analytic Reichardt mean
# profile mirrored across both walls (wall distance in viscous
# units y+ = (u*/nu) * min(y, 120 - y) = 3.0 * min(y, 120 - y)),
# seeded with wall-enveloped multi-mode sinusoidal perturbations
# (streamwise streaks plus cross-flow rolls carrying wall-normal
# velocity) at ~10% intensity to break the x/z symmetry and trip
# transition to turbulence. The sin(pi*y/120) envelope vanishes at
# both no-slip walls. The scalar is initialised separately via its
# `initial_field` ramp.
equations:
rho: "1"
ux: "0.005 * (2.5*log(1 + 0.41*3.0*Min(y, 120 - y)) + 7.8*(1 - exp(-3.0*Min(y, 120 - y)/11) - (3.0*Min(y, 120 - y)/11)*exp(-3.0*Min(y, 120 - y)/3))) + 0.010*sin(pi*y/120)*cos(2*pi*5*z/192) + 0.006*sin(pi*y/120)*sin(2*pi*3*x/360)*cos(2*pi*4*z/192)"
uy: "0.006*sin(pi*y/120)*sin(2*pi*3*x/360)*cos(2*pi*4*z/192) + 0.004*sin(pi*y/120)*sin(2*pi*2*x/360)*sin(2*pi*3*z/192)"
uz: "0.006*sin(pi*y/120)*sin(2*pi*3*x/360)*sin(2*pi*4*z/192) - 0.004*sin(pi*y/120)*cos(2*pi*2*x/360)*cos(2*pi*3*z/192)"
engine:
name: CUDA
BC:
periodic_dims: [true, false, true]
BC_map:
- pos: N
BC: RegularizedHWBB
wall_normal: N
order: 1
- pos: S
BC: RegularizedHWBB
wall_normal: S
order: 1
scalar_transports:
scalar:
velocity_set: D3Q27
collision_operator: RRBGK
# Turbulent Schmidt number for the LES SGS scalar diffusivity:
# D_total = D + nu_SGS / Sc_t.
Sc_t: 0.7
adv_diff_equation:
# nu / Pr = 1.6667e-3 / 0.71 = 2.3475e-3 in lattice units
# (tau_phi = 0.5 + 3*D = 0.50704).
D: 2.3475e-3
S: "0"
# Laminar steady-state ramp: phi(y) = y / Ly. Starts the
# scalar close to its eventual time-averaged mean.
initial_field: "y / 120.0"
BC:
BC_map:
- pos: S
BC: ScalarRegularizedDirichlet
wall_normal: S
order: 1
params:
phi_w: 0.0
- pos: N
BC: ScalarRegularizedDirichlet
wall_normal: N
order: 1
params:
phi_w: 1.0