from __future__ import print_function
%matplotlib inline
Tutorial: understand how FluidSim works
A goal of FluidSim is to be as simple as possible to allow anyone knowing a little bit of Python to understand how it works internally. For this tutorial, it is assumed that the reader knows how to run simulations with FluidSim. If it is not the case, first read the tutorial running a simulation (user perspective).
A class to organize parameters
First, we need to present the important class fluiddyn.util.paramcontainer.ParamContainer used to contain information.
from fluiddyn.util.paramcontainer import ParamContainer
params = ParamContainer(tag="params")
params._set_attribs({"a0": 1, "a1": 1})
params._set_attrib("a2", 1)
params._set_child("child0", {"a0": 1})
params.a2 = 2
params.child0.a0 = "other option"
A ParamContainer
can be represented as xml
params
<fluiddyn.util.paramcontainer.ParamContainer object at 0x7fdef4511650>
<params a0="1" a1="1" a2="2">
<child0 a0="other option"/>
</params>
FluidSim uses instances of this class to store the information of a particular solver and the parameters of a particular simulation.
The Simul classes and the default parameters
The first step to run a simulation is to import a Simul class from a solver module, for example:
from fluidsim.solvers.ns2d.solver import Simul
Any solver module has to define a class called Simul which has to have some important attributes:
[name for name in dir(Simul) if not name.startswith("__")]
['InfoSolver',
'Parameters',
'_abc_impl',
'_complete_params_with_default',
'compute_freq_diss',
'create_default_params',
'plot_freq_diss',
'tendencies_nonlin']
The first attribute InfoSolver
is a class deriving from fluidsim.base.solvers.info_base.InfoSolverBase
(which is a ParamContainer). This class is usually defined in the solver
module. It is used during the instantiation of the Simul object to produce a ParamContainer containing a description of the solver, in practice the names and the modules of the classes used for the different tasks that need to be performed during the simulation.
There are also four other functions. compute_freq_diss
and tendencies_nonlin
are used during the simulation and describe the equations that are solved.
create_default_params
and _complete_params_with_default
are used to produce the ParamContainer
containing the default parameters for a simulation:
params = Simul.create_default_params()
/home/docs/checkouts/readthedocs.org/user_builds/fluidsim/envs/latest/lib/python3.11/site-packages/fluidsim/operators/operators2d.py:35: UserWarning: operators2d.py has to be pythranized to be efficient! Install pythran and recompile.
warn(
During the creation of params
, the class InfoSolver
has been used to create a ParamContainer
named info_solver
:
Simul.info_solver
<fluidsim.solvers.ns2d.solver.InfoSolverNS2D object at 0x7fdeecfbff90>
<solver class_name="Simul" module_name="fluidsim.solvers.ns2d.solver"
short_name="NS2D">
<classes>
<Operators class_name="OperatorsPseudoSpectral2D"
module_name="fluidsim.operators.operators2d"/>
<State class_name="StateNS2D" keys_computable="['div', 'q', 'v']"
keys_linear_eigenmodes="['rot_fft']" keys_phys_needed="['rot']"
keys_state_phys="['ux', 'uy', 'rot']" keys_state_spect="['rot_fft']"
module_name="fluidsim.solvers.ns2d.state"/>
<TimeStepping class_name="TimeSteppingPseudoSpectral"
module_name="fluidsim.base.time_stepping.pseudo_spect"/>
<InitFields class_name="InitFieldsNS2D"
module_name="fluidsim.solvers.ns2d.init_fields">
<classes>
<from_file class_name="InitFieldsFromFile"
module_name="fluidsim.base.init_fields"/>
<from_simul class_name="InitFieldsFromSimul"
module_name="fluidsim.base.init_fields"/>
<in_script class_name="InitFieldsInScript"
module_name="fluidsim.base.init_fields"/>
<constant class_name="InitFieldsConstant"
module_name="fluidsim.base.init_fields"/>
<noise class_name="InitFieldsNoise"
module_name="fluidsim.solvers.ns2d.init_fields"/>
<jet class_name="InitFieldsJet"
module_name="fluidsim.solvers.ns2d.init_fields"/>
<dipole class_name="InitFieldsDipole"
module_name="fluidsim.solvers.ns2d.init_fields"/>
</classes>
</InitFields>
<Forcing class_name="ForcingNS2D"
module_name="fluidsim.solvers.ns2d.forcing">
<classes>
<tcrandom_anisotropic
class_name="TimeCorrelatedRandomPseudoSpectralAnisotropic"
module_name="fluidsim.base.forcing.anisotropic"/>
<milestone class_name="ForcingMilestone"
module_name="fluidsim.base.forcing.milestone"/>
<in_script class_name="InScriptForcingPseudoSpectral"
module_name="fluidsim.base.forcing.specific"/>
<in_script_coarse class_name="InScriptForcingPseudoSpectralCoarse"
module_name="fluidsim.base.forcing.specific"/>
<proportional class_name="Proportional"
module_name="fluidsim.base.forcing.specific"/>
<tcrandom class_name="TimeCorrelatedRandomPseudoSpectral"
module_name="fluidsim.base.forcing.specific"/>
</classes>
</Forcing>
<Output class_name="Output" module_name="fluidsim.solvers.ns2d.output">
<classes>
<PrintStdOut class_name="PrintStdOutNS2D"
module_name="fluidsim.solvers.ns2d.output.print_stdout"/>
<PhysFields class_name="PhysFieldsBase2D"
module_name="fluidsim.base.output.phys_fields2d"/>
<Spectra class_name="SpectraNS2D"
module_name="fluidsim.solvers.ns2d.output.spectra"/>
<SpectraMultiDim class_name="SpectraMultiDimNS2D"
module_name="fluidsim.solvers.ns2d.output.spectra_multidim"/>
<SpatialMeans class_name="SpatialMeansNS2D"
module_name="fluidsim.solvers.ns2d.output.spatial_means"/>
<SpectEnergyBudg class_name="SpectralEnergyBudgetNS2D"
module_name="fluidsim.solvers.ns2d.output.spect_energy_budget"/>
<Increments class_name="Increments"
module_name="fluidsim.base.output.increments"/>
<TemporalSpectra class_name="TemporalSpectra2D"
module_name="fluidsim.base.output.temporal_spectra"/>
<SpatioTemporalSpectra class_name="SpatioTemporalSpectraNS2D"
module_name="fluidsim.solvers.ns2d.output.spatiotemporal_spectra"/>
</classes>
</Output>
<Preprocess class_name="PreprocessPseudoSpectral"
module_name="fluidsim.base.preprocess.pseudo_spect">
<classes/>
</Preprocess>
</classes>
</solver>
We see that this solver uses many classes and that they are organized in tasks (“Operator”, “InitFields”, “TimeStepping”, “State”, “Output”, “Forcing”). Some first-level classes (for example “Output”) have second-level classes (“PrintStdOut”, “Spectra”, “PhysFields”, etc.). Such description of a solver is very general. It is also very conveniant to create a new solver from a similar existing solver.
Every classes can have a class function or a static function _complete_params_with_default
that is called when the object containing the default parameters is created.
The objects params
and Simul.info_solver
are then used to instantiate the simulation (here with the default parameters for the solver):
sim = Simul(params)
*************************************
Program fluidsim
sim: <class 'fluidsim.solvers.ns2d.solver.Simul'>
sim.output: <class 'fluidsim.solvers.ns2d.output.Output'>
sim.oper: <class 'fluidsim.operators.operators2d.OperatorsPseudoSpectral2D'>
sim.state: <class 'fluidsim.solvers.ns2d.state.StateNS2D'>
sim.time_stepping: <class 'fluidsim.base.time_stepping.pseudo_spect.TimeSteppingPseudoSpectral'>
sim.init_fields: <class 'fluidsim.solvers.ns2d.init_fields.InitFieldsNS2D'>
solver NS2D, RK4 and sequential,
type fft: fluidfft.fft2d.with_pyfftw
nx = 48 ; ny = 48
lx = 8 ; ly = 8
path_run =
/home/docs/Sim_data/NS2D_48x48_S8x8_2025-01-20_14-21-57
init_fields.type: constant
Initialization outputs:
sim.output.increments: <class 'fluidsim.base.output.increments.Increments'>
sim.output.phys_fields: <class 'fluidsim.base.output.phys_fields2d.PhysFieldsBase2D'>
sim.output.spatial_means: <class 'fluidsim.solvers.ns2d.output.spatial_means.SpatialMeansNS2D'>
sim.output.spatiotemporal_spectra:<class 'fluidsim.solvers.ns2d.output.spatiotemporal_spectra.SpatioTemporalSpectraNS2D'>
sim.output.spect_energy_budg: <class 'fluidsim.solvers.ns2d.output.spect_energy_budget.SpectralEnergyBudgetNS2D'>
sim.output.spectra: <class 'fluidsim.solvers.ns2d.output.spectra.SpectraNS2D'>
sim.output.spectra_multidim: <class 'fluidsim.solvers.ns2d.output.spectra_multidim.SpectraMultiDimNS2D'>
sim.output.temporal_spectra: <class 'fluidsim.base.output.temporal_spectra.TemporalSpectra2D'>
Memory usage at the end of init. (equiv. seq.): 221.7421875 Mo
Size of state_spect (equiv. seq.): 0.0192 Mo
Let’s print the attributes of sim
that are not class attributes:
[name for name in dir(sim) if not name.startswith("_") and name not in dir(Simul)]
['info',
'init_fields',
'is_forcing_enabled',
'name_run',
'oper',
'output',
'params',
'preprocess',
'state',
'time_stepping']
Except name_run
and info
, the attributes are instances of the first-level classes defined in Simul.info_solver
. These different objects have to interact together. We are going to present these different hierarchies of classes but first we come back to the two functions describing the equations in a pseudo-spectral solver.
Description of the solved equations
The functions Simul.compute_freq_diss
and Simul.tendencies_nonlin
define the solved equations. Looking at the documentation of the solver module fluidsim.solvers.ns2d.solver
, we see that Simul.tendencies_nonlin
is defined in this module and that Simul.compute_freq_diss
is inherited from the base class fluidsim.base.solvers.pseudo_spect.SimulBasePseudoSpectral
. By clicking on these links, you can look at the documentation and the sources of these functions. The documentation explains how this function define the solved equations. I think the sources are quite clear and can be understood by anyone knowing a little bit of Python for science. Most of the objects involved in these functions are functions or numpy.ndarray.
State classes (sim.state
)
sim.state
is an instance of fluidsim.solvers.ns2d.state.StateNS2D
. It contains numpy.ndarray
, actually slightly modified numpy.ndarray
named fluidsim.base.setofvariables.SetOfVariables
. This class is used to stack variables together in a single numpy.ndarray
.
The state classes are also able to compute other variables from the state of the simulation. It is an interface hiding the actual way the data are stored.
Operator classes (sim.oper
)
sim.oper
is an instance of fluidsim.operators.operators2d.OperatorsPseudoSpectral2D
.
It contains the information on the grids (in physical and spectral space) and provides many optimized functions on arrays representing fields on these grids.
It has to be fast! For the two dimensional Fourier pseudo-spectral solvers, it is written in Cython.
TimeStepping classes (sim.time_stepping
)
sim.time_stepping
is an instance of fluidsim.base.time_stepping.pseudo_spect.TimeSteppingPseudoSpectral
, which is based on fluidsim.base.time_stepping.base.TimeSteppingBase
.
This class contains the functions for the time advancement, i.e. Runge-Kutta functions and the actual loop than increments the time stepping index sim.time_stepping.it
. The Runge-Kutta functions call the function sim.tendencies_nonlin
and modify the state in Fourier space sim.state.state_fft
.
The loop function also call the function sim.output.one_time_step
.
Output classes (sim.output
)
sim.output
is an instance of fluidsim.solvers.ns2d.output.Output
.
Saving and plotting of online or on-the-fly postprocessed data - i.e., data generated by processing the solver state variables at regular intervals during simulation time. It could include physical fields, spatially averaged means, spectral energy budgets, PDFs etc.
Forcing classes (sim.forcing
)
sim.forcing
is an instance of fluidsim.solvers.ns2d.forcing.ForcingNS2D
.
If params.forcing.enable
is True, it is used in sim.tendencies_nonlin
to add the forcing term.