{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from __future__ import print_function\n", "\n", "%matplotlib inline\n", "import fluidsim" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Tutorial: running a simulation (user perspective)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this tutorial, I'm going to show how to run a simple simulation with a solver that solves the 2 dimensional Navier-Stokes equations. I'm also going to present some useful concepts and objects used in FluidSim." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## A minimal simulation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, let's see what is needed to run a very simple simulation. For the initialization (with default parameters):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from fluidsim.solvers.ns2d.solver import Simul" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "params = Simul.create_default_params()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "params" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim = Simul(params)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And then to run the simulation:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim.time_stepping.start()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the following, we are going to understand these 4 lines of code... But first let's clean-up by deleting the result directory of this tiny example simulation:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import shutil\n", "\n", "shutil.rmtree(sim.output.path_run)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Importing a solver" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first line imports a \"Simulation\" class from a \"solver\" module. Any solver module has to provide a class called \"Simul\". We have already seen that the Simul class can be imported like this:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from fluidsim.solvers.ns2d.solver import Simul" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "but there is another convenient way to import it from a string:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "Simul = fluidsim.import_simul_class_from_key(\"ns2d\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create an instance of the class Parameters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The next step is to create an object ``params`` from the information contained in the class ``Simul``:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "params = Simul.create_default_params()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The object `params` is an instance of the class :class:`fluidsim.base.params.Parameters` (which inherits from [fluiddyn.util.paramcontainer.ParamContainer](http://fluiddyn.readthedocs.org/en/latest/generated/fluiddyn.util.paramcontainer.html)). It is usually a quite complex hierarchical object containing many attributes. To print them, the normal way would be to use the tab-completion of Ipython, i.e. to type \"`params.`\" and press on the tab key. Here with Jupyter, I can not do that so I'm going to use a command that produce a list with the interesting attributes. If you don't understand this command, you should have a look at the section on [list comprehensions](https://docs.python.org/2/tutorial/datastructures.html#list-comprehensions) of the official Python tutorial):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[attr for attr in dir(params) if not attr.startswith(\"_\")]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "and some useful functions (whose names all start with ``_`` in order to be hidden in Ipython and not mixed with the meaningful parameters): " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[\n", " attr\n", " for attr in dir(params)\n", " if attr.startswith(\"_\") and not attr.startswith(\"__\")\n", "]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Some of the attributes of `params` are simple Python objects and others can be other :class:`fluidsim.base.params.Parameters`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(type(params.nu_2))\n", "print(type(params.output))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[attr for attr in dir(params.output) if not attr.startswith(\"_\")]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see that the object `params` contains a tree of parameters. This tree can be represented as xml code:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(params)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Set the parameters for your simulation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The user can change any parameters" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "params.nu_2 = 1e-3\n", "params.forcing.enable = False\n", "\n", "params.init_fields.type = \"noise\"\n", "\n", "params.output.periods_save.spatial_means = 1.0\n", "params.output.periods_save.spectra = 1.0\n", "params.output.periods_save.phys_fields = 2.0" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "but it is impossible to create accidentally a parameter which is actually not used:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "try:\n", " params.this_param_does_not_exit = 10\n", "except AttributeError as e:\n", " print(\"AttributeError:\", e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And you also get an explicit error message if you use a nonexistent parameter:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "try:\n", " print(params.this_param_does_not_exit)\n", "except AttributeError as e:\n", " print(\"AttributeError:\", e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This behaviour is much safer than using a text file or a python file for the parameters. In order to discover the different parameters for a solver, create the `params` object containing the default parameters in Ipython (`params = Simul.create_default_params()`), print it and use the auto-completion (for example writting `params.` and pressing on the tab key)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Instantiate a simulation object" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The next step is to create a simulation object (an instance of the class `solver.Simul`) with the parameters in `params`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim = Simul(params)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "which initializes everything needed to run the simulation. \n", "\n", "The log shows the object-oriented structure of the solver. Every task is performed by an object of a particular class. Of course, you don't need to understand the structure of the solver to run simulations but soon it's going to be useful to understand what you do and how to interact with fluidsim objects.\n", "\n", "The object `sim` has a limited number of attributes:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[attr for attr in dir(sim) if not attr.startswith(\"_\")]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the tutorial [Understand how FluidSim works](tuto_dev.ipynb), we will see what are all these attributes.\n", "\n", "The object `sim.info` is a :class:`fluiddyn.util.paramcontainer.ParamContainer` which contains all the information on the solver (in `sim.info.solver`) and on specific parameters for this simulation (in `sim.info.solver`):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(sim.info.__class__)\n", "print([attr for attr in dir(sim.info) if not attr.startswith(\"_\")])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim.info.solver is sim.info_solver" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim.info.params is sim.params" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(sim.info.solver)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see that a solver is defined by the classes it uses for some tasks. The tutorial [Understand how FluidSim works](tuto_dev.ipynb) is meant to explain how." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Run the simulation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now start the time stepping. Since ``params.time_stepping.USE_T_END is True``, it should loop until ``sim.time_stepping.t`` is equal or larger than ``params.time_stepping.t_end = 10``." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim.time_stepping.start()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Analyze the output" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's see what we can do with the object ``sim.output``. What are its attributes?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "[attr for attr in dir(sim.output) if not attr.startswith(\"_\")]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Many of these objects (`print_stdout`, `phys_fields`, `spatial_means`, `spect_energy_budg`, `spectra`, ...) were used during the simulation to save outputs. They can also load the data and produce some simple plots. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's say that it is very simple to reload an old simulation from the saved files. There are two convenient functions to do this `fluidsim.load_sim_for_plot` and `fluidsim.load_state_phys_file`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from fluidsim import load_sim_for_plot" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(load_sim_for_plot.__doc__)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from fluidsim import load_state_phys_file" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(load_state_phys_file.__doc__)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim = load_state_phys_file(sim.output.path_run)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For example, to display the time evolution of spatially averaged quantities (here the energy, the entrophy and their dissipation rate):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim.output.spatial_means.plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To plot the final state:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim.output.phys_fields.plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And a different time:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim.output.phys_fields.plot(time=4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can even plot variables that are not in the state in the solver. For example, in this solver, the divergence, which should be equal to 0:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "sim.output.phys_fields.plot(\"div\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally we remove the directory of this example simulation..." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "shutil.rmtree(sim.output.path_run)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.6" } }, "nbformat": 4, "nbformat_minor": 4 }