Advanced Features
FluidSim supports several forcing mechanisms to maintain turbulence or drive specific dynamics.
Overview
Advanced Features
Custom Forcing
Forcing Types
FluidSim supports several forcing mechanisms to maintain turbulence or drive specific dynamics.
Time-Correlated Random Forcing
Most common for sustained turbulence:
params.forcing.enable = True
params.forcing.type = "tcrandom"
params.forcing.nkmin_forcing = 2 # minimum forced wavenumber
params.forcing.nkmax_forcing = 5 # maximum forced wavenumber
params.forcing.forcing_rate = 1.0 # energy injection rate
params.forcing.tcrandom_time_correlation = 1.0 # correlation time
Proportional Forcing
Maintains a specific energy distribution:
params.forcing.type = "proportional"
params.forcing.forcing_rate = 1.0
Custom Forcing in Script
Define forcing directly in the launch script:
params.forcing.enable = True
params.forcing.type = "in_script"
sim = Simul(params)
# Define custom forcing function
def compute_forcing_fft(sim):
"""Compute forcing in Fourier space"""
forcing_vx_fft = sim.oper.create_arrayK(value=0.)
forcing_vy_fft = sim.oper.create_arrayK(value=0.)
# Add custom forcing logic
# Example: force specific modes
forcing_vx_fft[10, 10] = 1.0 + 0.5j
return forcing_vx_fft, forcing_vy_fft
# Override forcing method
sim.forcing.forcing_maker.compute_forcing_fft = lambda: compute_forcing_fft(sim)
# Run simulation
sim.time_stepping.start()
Custom Initial Conditions
In-Script Initialization
Full control over initial fields:
from math import pi
params = Simul.create_default_params()
params.oper.nx = params.oper.ny = 256
params.oper.Lx = params.oper.Ly = 2 * pi
params.init_fields.type = "in_script"
sim = Simul(params)
# Get coordinate arrays
X, Y = sim.oper.get_XY_loc()
# Define velocity fields
vx = sim.state.state_phys.get_var("vx")
vy = sim.state.state_phys.get_var("vy")
# Taylor-Green vortex
vx[:] = np.sin(X) * np.cos(Y)
vy[:] = -np.cos(X) * np.sin(Y)
# Initialize state in Fourier space
sim.state.statephys_from_statespect()
# Run simulation
sim.time_stepping.start()
Layer Initialization (Stratified Flows)
Set up density layers:
from fluidsim.solvers.ns2d.strat.solver import Simul
params = Simul.create_default_params()
params.N = 1.0 # stratification
params.init_fields.type = "in_script"
sim = Simul(params)
# Define dense layer
X, Y = sim.oper.get_XY_loc()
b = sim.state.state_phys.get_var("b") # buoyancy field
# Gaussian density anomaly
x0, y0 = pi, pi
sigma = 0.5
b[:] = np.exp(-((X - x0)**2 + (Y - y0)**2) / (2 * sigma**2))
sim.state.statephys_from_statespect()
sim.time_stepping.start()
Parallel Computing with MPI
Running MPI Simulations
Install with MPI support:
uv pip install "fluidsim[fft,mpi]"
Run with MPI:
mpirun -np 8 python simulation_script.py
FluidSim automatically detects MPI and distributes computation.
MPI-Specific Parameters
# No special parameters needed
# FluidSim handles domain decomposition automatically
# For very large 3D simulations
params.oper.nx = 512
params.oper.ny = 512
params.oper.nz = 512
# Run with: mpirun -np 64 python script.py
Output with MPI
Output files are written from rank 0 processor. Analysis scripts work identically for serial and MPI runs.
Parametric Studies
Running Multiple Simulations
Script to generate and run multiple parameter combinations:
from fluidsim.solvers.ns2d.solver import Simul
# Parameter ranges
viscosities = [1e-3, 5e-4, 1e-4, 5e-5]
resolutions = [128, 256, 512]
for nu in viscosities:
for nx in resolutions:
params = Simul.create_default_params()
# Configure simulation
params.oper.nx = params.oper.ny = nx
params.nu_2 = nu
params.time_stepping.t_end = 10.0
# Unique output directory
params.output.sub_directory = f"nu{nu}_nx{nx}"
# Run simulation
sim = Simul(params)
sim.time_stepping.start()
Cluster Submission
Submit multiple jobs to a cluster:
from fluiddyn.clusters.legi import Calcul8 as Cluster
cluster = Cluster()
for nu in viscosities:
for nx in resolutions:
script_content = f"""
from fluidsim.solvers.ns2d.solver import Simul
params = Simul.create_default_params()
params.oper.nx = params.oper.ny = {nx}
params.nu_2 = {nu}
params.time_stepping.t_end = 10.0
params.output.sub_directory = "nu{nu}_nx{nx}"
sim = Simul(params)
sim.time_stepping.start()
"""
with open(f"job_nu{nu}_nx{nx}.py", "w") as f:
f.write(script_content)
cluster.submit_script(
f"job_nu{nu}_nx{nx}.py",
name_run=f"sim_nu{nu}_nx{nx}",
nb_nodes=1,
nb_cores_per_node=24,
walltime="12:00:00"
)
Analyzing Parametric Studies
from fluidsim import load_sim_for_plot
results = []
# Collect data from all simulations
for sim_dir in os.listdir("simulations"):
sim_path = f"simulations/{sim_dir}"
if not os.path.isdir(sim_path):
continue
try:
sim = load_sim_for_plot(sim_path)
# Extract parameters
nu = sim.params.nu_2
nx = sim.params.oper.nx
# Extract results
df = sim.output.spatial_means.load()
final_energy = df["E"].iloc[-1]
mean_energy = df["E"].mean()
results.append({
"nu": nu,
"nx": nx,
"final_energy": final_energy,
"mean_energy": mean_energy
})
except Exception as e:
print(f"Error loading {sim_dir}: {e}")
# Analyze results
results_df = pd.DataFrame(results)
# Plot results
plt.figure(figsize=(10, 6))
for nx in results_df["nx"].unique():
subset = results_df[results_df["nx"] == nx]
plt.plot(subset["nu"], subset["mean_energy"],
marker="o", label=f"nx={nx}")
plt.xlabel("Viscosity")
plt.ylabel("Mean Energy")
plt.xscale("log")
plt.legend()
plt.savefig("parametric_study_results.png")
Custom Solvers
Extending Existing Solvers
Create a new solver by inheriting from an existing one:
from fluidsim.solvers.ns2d.solver import Simul as SimulNS2D
from fluidsim.base.setofvariables import SetOfVariables
class SimulCustom(SimulNS2D):
"""Custom solver with additional physics"""
@staticmethod
def _complete_params_with_default(params):
"""Add custom parameters"""
SimulNS2D._complete_params_with_default(params)
params._set_child("custom", {"param1": 0.0})
def __init__(self, params):
super().__init__(params)
# Custom initialization
def tendencies_nonlin(self, state_spect=None):
"""Override to add custom tendencies"""
tendencies = super().tendencies_nonlin(state_spect)
# Add custom terms
# tendencies.vx_fft += custom_term_vx
# tendencies.vy_fft += custom_term_vy
return tendencies
Use the custom solver:
params = SimulCustom.create_default_params()
# Configure params...
sim = SimulCustom(params)
sim.time_stepping.start()
Online Visualization
Display fields during simulation:
params.output.ONLINE_PLOT_OK = True
params.output.periods_plot.phys_fields = 1.0 # plot every 1.0 time units
params.output.phys_fields.field_to_plot = "vorticity"
sim = Simul(params)
sim.time_stepping.start()
Plots appear in real-time during execution.
Checkpoint and Restart
Automatic Checkpointing
params.output.periods_save.phys_fields = 1.0 # save every 1.0 time units
Fields are saved automatically during simulation.
Manual Checkpointing
# During simulation
sim.output.phys_fields.save()
Restarting from Checkpoint
params = Simul.create_default_params()
params.init_fields.type = "from_file"
params.init_fields.from_file.path = "simulation_dir/state_phys_t5.000.h5"
params.time_stepping.t_end = 20.0 # extend simulation
sim = Simul(params)
sim.time_stepping.start()
Memory and Performance Optimization
Reduce Memory Usage
# Disable unnecessary outputs
params.output.periods_save.spectra = 0 # disable spectra saving
params.output.periods_save.spect_energy_budg = 0 # disable energy budget
# Reduce spatial field saves
params.output.periods_save.phys_fields = 10.0 # save less frequently
Optimize FFT Performance
# Select FFT library
os.environ["FLUIDSIM_TYPE_FFT2D"] = "fft2d.with_fftw"
os.environ["FLUIDSIM_TYPE_FFT3D"] = "fft3d.with_fftw"
# Or use MKL if available
# os.environ["FLUIDSIM_TYPE_FFT2D"] = "fft2d.with_mkl"
Time Step Optimization
# Use adaptive time stepping
params.time_stepping.USE_CFL = True
params.time_stepping.CFL = 0.8 # slightly larger CFL for faster runs
# Use efficient time scheme
params.time_stepping.type_time_scheme = "RK4" # 4th order Runge-Kutta