Closure: Smagorinsky on Slurm¶
Declare a native symbolic Smagorinsky operator and couple it through the nutFjord LES adapter in a Pitz Daily case.
Before running¶
Install FoamNordic, build its integration and check the OpenFOAM environment. Start inside a FoamNordic checkout containing the tutorial cases. Runs are copied below the notebook output directory.
This example uses Slurm and module load openfoam/2512 with bash. Replace the allocation account, partition and resources with local values. The supplied tutorial case and the built nutFjord adapter are required. The mathematical closure uses FoamNordic algebra and requires no trained-model download.
Read or download the original notebook. Run the original cells below in order.
The code shows the main workload choices and leaves advanced execution options at their defaults; see the execution reference. Figure styling uses Hedgehogs helpers on Matplotlib axes.
Preamble¶
# Standard Library
from pathlib import Path
import foamnordic as fno
import hedgehogs as hdg
Directories¶
# Case Type and Name
CASE_TYPE = "les"
CASE_NAME = "pitzDailySmag"
CATEGORY = "incompressible"
# FoamNordic Project Directory
PROJECT_DIR = Path.cwd().resolve()
BASE_DIR = next((path for path in (PROJECT_DIR, *PROJECT_DIR.parents) if (path / "tutorials" / "openfoam_tutorials").is_dir()), None)
if BASE_DIR is None:
raise FileNotFoundError("Run this notebook from inside the FoamNordic source tree")
MAIN_DIR = BASE_DIR / "tutorials" / "foamnordic_tutorials" / CATEGORY
OF_SCRIPT_DIR = BASE_DIR / "tutorials" / "openfoam_tutorials" / CATEGORY / CASE_TYPE / CASE_NAME
# Output Directory
MODEL_DIR = MAIN_DIR / "model"
OUTPUT_DIR = MAIN_DIR / "output"
for directory in [MODEL_DIR, OUTPUT_DIR]:
directory.mkdir(parents=True, exist_ok=True)
Configuration¶
# Hedgehogs configuration
FIG_X, FIG_Y = hdg.figsize()
FIGURE_SIZE = (FIG_X, FIG_Y)
PALETTE = hdg.get_palette()
hdg.set_style()
# HPC configuration
ACCOUNT = "<allocation-account>" # e.g. project_xxxxxxx
PARTITION = "small"
TIME = "00:15:00"
N_NODES = 1
N_TASKS = 1
CPUS_PER_TASK = 1
MEM_PER_CPU = "2G"
# FoamNordic Slurm configuration
of_scheduler = fno.Slurm.openfoam(
nodes=N_NODES,
ntasks=N_TASKS,
cpus_per_task=CPUS_PER_TASK,
mem_per_cpu=MEM_PER_CPU,
)
model_scheduler = fno.Slurm.model(
cpus_per_task=1,
mem_per_cpu=MEM_PER_CPU
)
scheduler = fno.Slurm(
account=ACCOUNT,
partition=PARTITION,
time=TIME,
openfoam=of_scheduler,
model=model_scheduler
)
Example - Closure Modelling¶
Smagorinsky Closure¶
# Smagorinsky model coefficients
C_K = 0.0265463553
C_E = 1.048
# Smagorinsky function for subgrid-scale turbulence modelling
def smagorinsky_function(velocity_grad, delta, C_k=C_K, C_e=C_E):
strain_rate = fno.Math.symm(value=velocity_grad)
strain_rate_trace = fno.Math.trace(value=strain_rate)
dev_strain_rate = fno.Math.dev(value=strain_rate)
coefficient_a = C_e / delta
coefficient_b = (2.0 / 3.0) * strain_rate_trace
coefficient_c = 2.0 * C_k * delta * fno.Math.ddot(left=dev_strain_rate, right=strain_rate)
discriminant = coefficient_b**2 + 4.0 * coefficient_a * coefficient_c
sqrt_k = (-coefficient_b + fno.Math.sqrt(value=fno.Math.maximum(left=discriminant, right=0.0))) / (2.0 * coefficient_a)
sqrt_k = fno.Math.maximum(left=sqrt_k, right=0.0)
eddy_viscosity = C_k * delta * sqrt_k
return eddy_viscosity
# Define the Smagorinsky closure
smagorinsky_closure = fno.Closure(
name="nutFjord",
operator=fno.Operator.function(function=smagorinsky_function),
inputs={
"velocity_grad": fno.Field.grad(value="U"),
"delta": fno.Field.delta(),
},
outputs={
"eddy_viscosity": fno.Field(name="nut"),
},
key=None,
)
Case Definition¶
# Initialize the OpenFOAM case
case = fno.OpenFOAM.Case(
name=CASE_NAME,
case_dir=OF_SCRIPT_DIR,
run_dir=OUTPUT_DIR,
of_cmd="module load openfoam/2512",
shell="bash",
application="pimpleFoam",
ranks=N_TASKS,
)
case.initialize(ranks=N_TASKS, mesh="blockMesh", validate_mesh=True);
Submit Job¶
# Connect the OpenFOAM case and execution resources
longship = fno.Longship(
case=case,
closures=(smagorinsky_closure,),
scheduler=scheduler,
verbose=True,
)
# Launch the workload
run = longship.launch(verbose=None)
# Wait for the job to complete (polling the job status)
result = run.stop(force=False, timeout=3600, progress=True)
# Summary of the job result
result.summary(style="compact", display=True);
Post-processing¶
# Postprocessing
post = result.postprocess
velocity = post.field(name="U", time_idx=-1, physical_time=None)
pressure = post.field(name="p", time_idx=-1, physical_time=None)
print("U shape:", velocity.shape)
print("p shape:", pressure.shape)
statistics = post.statistics(
fields=["U", "p", "nut"],
time_idx=-1,
physical_time=None,
verbose=True,
)
Check the result¶
Check successful completion and postprocessed U and p. The example demonstrates the closure integration path; it does not establish a new turbulence-model validation result.