.. _guided_heat_simple: Tutorial: Heat Equation - Simple ================================== .. admonition:: **Time to Complete**: 25 mins :class: warning **GOALS:** - Compile an AMReX Code - Introduce Basic AMReX Elements - Generate and Visualize Output In this tutorial we take the steps needed to go from source download to visualized output of an AMReX code. We will demonstrate basic building, compiling and output generation. We will also examine several key AMReX features in a C++ code and plot the output with Python in a Jupyter notebook. Setting Up Your Environment ~~~~~~~~~~~~~~~~~~~~~~~~~~~ This tutorial recommends using Gitpod (Requires a GitHub account). Gitpod provides an online terminal that is already preconfigured for our development environment. Click |GitpodLink| to be taken to the Gitpod workspace. .. |GitpodLink| raw:: html here Building the Project ~~~~~~~~~~~~~~~~~~~~ This example will use CMake to build the project. Navigate to the directory :code:`/workspace/amrex-tutorials/GuidedTutorials/HeatEquation_Simple/` and type .. code-block:: mkdir Build cd Build cmake .. This will create a build folder and run CMake to setup the build configuration. During this process CMake will read the ``CmakeLists.txt`` file in the subdirectory to generate and download, if necessary, the the build files it needs to compile the tutorial in the next step. More information about building options, such as disabling MPI, can be found at https://amrex-codes.github.io/amrex/docs_html/BuildingAMReX_Chapter.html. Compiling the Code with CMake ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ After building the project, in the ``Build`` directory you will find several new files and directories created by CMake during the configuration process. At the prompt type :: cmake --build . -j2 CMake will then compile the code and dependencies. The ``-j2`` flag tells CMake to use 2 processes to speed up compilation. The first time you call :code:`cmake --build .` you should see a list of all the source AMReX files being compiled: .. image:: ./images_tutorial/Cmake_make.png | When CMake finishes you will be left with an executable named :code:`HeatEquation_Simple`. To run the code type: .. code-block:: ./HeatEquation_Simple inputs This command will run the :code:`HeatEquation_Simple` code with the :code:`inputs` file as the input parameters. Parsing of the information in the :code:`inputs` file is done by :code:`ParmParse`. More details can be found at https://amrex-codes.github.io/amrex/docs_html/Basics.html#parmparse Code Highlights ~~~~~~~~~~~~~~~ At this point we have built, compiled and ran the :code:`HeatEquation_Simple` code. Now we will walk through the code and explain some essential features of AMReX syntax. Basic Structure ^^^^^^^^^^^^^^^ :: Main ├──── Initialize AMReX ├──── Declare Simulation Parameters ├──── Read Parameter Values From Input File ├──── Define Simulation Setup & Geometry ├──── Initialize Data Loop │ └──── Set Values For Each Cell ├──── Write Initial Plotfile ├──── Main Time Evolution Loop │ ├──── Evolve Values For Each Cell │ ├──── Increment │ └──── Write Plotfile At Given Interval └──── Finalize AMReX AMReX Namespace and Required Commands ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The AMReX namespace contains many useful features. They are accessed by including the necessary header files and using the prefix :code:`amrex::`. Each :code:`int main(...)` using AMReX should begin with :code:`amrex::Initialize()` immediately followed by :code:`{` and end with :code:`}` immediately followed by :code:`amrex::Finalize()`. Together these commands are responsible for initializing the AMReX execution environment and proper release of resources. AMReX classes and features not located between the commands will not function properly. Other useful features include :code:`amrex::Print()` which was written to handle print output during parallel execution. The MultiFab Data Structure ^^^^^^^^^^^^^^^^^^^^^^^^^^^ A :code:`MultiFab` is a data structure that AMReX can distribute among parallel processes. In this Heat Equation example we use two MultiFabs to hold the current and previous values of :math:`\phi` as defined `here`_. .. _`here` : https://amrex-codes.github.io/amrex/docs_html/GettingStarted.html#example-heat-equation-solver The declaration of the first MultiFab for the previous values of :math:`\phi` is: .. code-block:: amrex::MultiFab phi_old(ba, dm, Ncomp, Nghost); Here :code:`ba` is a `BoxArray`_ that stores a collection of boxes on a single level of mesh refinement. :code:`dm` is a `DistributionMapping`_ that describes how to distribute processing across multiple CPUs and threads. :code:`Ncomp` is the number of values stored for each cell of the mesh; in this case, 1 for the scalar :math:`\phi`. The value for :code:`Nghost` tells AMReX how many `ghost cells`_ to create outside the box's valid region. .. _`BoxArray`: https://amrex-codes.github.io/amrex/docs_html/Basics.html#boxarray .. _`DistributionMapping`: https://amrex-codes.github.io/amrex/docs_html/Basics.html#distributionmapping .. _`ghost cells`: https://amrex-codes.github.io/amrex/docs_html/Basics.html#ghost-cells MFIter and ParallelFor ^^^^^^^^^^^^^^^^^^^^^^ Now we will examine the main time evolution loop. In this section AMReX's :code:`MFIter` and :code:`ParallelFor` constructs work in conjunction to provide efficient parallel execution. The code where this happens is: .. code-block:: for (int step = 1; step <= nsteps; ++step){ phi_old.FillBoundary(geom.periodicity()); for ( amrex::MFIter mfi(phi_old); mfi.isValid(); ++mfi ){ const amrex::Box& bx = mfi.validbox(); const amrex::Array4& phiOld = phi_old.array(mfi); const amrex::Array4& phiNew = phi_new.array(mfi); amrex::ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k){ phiNew(i,j,k) = phiOld(i,j,k) + dt * ( (phiOld(i+1,j,k) - 2.*phiOld(i,j,k) + phiOld(i-1,j,k)) / (dx[0]*dx[0]) +(phiOld(i,j+1,k) - 2.*phiOld(i,j,k) + phiOld(i,j-1,k)) / (dx[1]*dx[1]) +(phiOld(i,j,k+1) - 2.*phiOld(i,j,k) + phiOld(i,j,k-1)) / (dx[2]*dx[2]) ); }); // end ParallelFor } time = time + dt; amrex::MultiFab::Copy(phi_old, phi_new, 0, 0, 1, 0); amrex::Print() << "Advanced step " << step << "\n"; if (plot_int > 0 && step%plot_int == 0){ const std::string& pltfile = amrex::Concatenate("plt",step,5); WriteSingleLevelPlotfile(pltfile, phi_new, {"phi"}, geom, time, step); } } First note the outer :code:`for` loop that counts the time step in our simulation. At each step we begin by calling :code:`phi_old.FillBoundary(geom.periodicity())`. This fills ghost cells based on the previous state of :math:`\phi` with periodic boundary conditions. MFIter """""" The next :code:`for` loop, .. code-block:: for ( amrex::MFIter mfi(phi_old); mfi.isValid(); ++mfi ) uses the data object :code:`MFIter` to separate the mesh across processes for individual operations. Within this loop the active piece of the mesh is defined by :code:`mfi.validbox()` and is accessed via :code:`bx` on the line, .. code-block:: const amrex::Box& bx = mfi.validbox(); In the next lines, the part of :code:`MultiFab` data that pertains to the current active piece of the mesh is converted to an `Array4`_ data type for i,j,k access: .. _`Array4`: https://amrex-codes.github.io/amrex/docs_html/Basics.html?highlight=array4#basefab-farraybox-iarraybox-and-array4 .. code-block:: const amrex::Array4& phiOld = phi_old.array(mfi); const amrex::Array4& phiNew = phi_new.array(mfi); ParallelFor """"""""""" :code:`ParallelFor` provides parallel execution of i,j,k operations that would otherwise require three nested loops. This AMReX construct automatically adapts for efficient computation based on the available hardware, including CPU and CPU+GPU variations. In this example, it is here we compute the forward Euler step (see `Heat Eqn`_) with the code: .. _`Heat Eqn`: https://amrex-codes.github.io/amrex/docs_html/GettingStarted.html#example-heat-equation-solver .. code-block:: amrex::ParallelFor(bx, [=] AMREX_GPU_DEVICE (int i, int j, int k){ phiNew(i,j,k) = phiOld(i,j,k) + dt * ( (phiOld(i+1,j,k) - 2.*phiOld(i,j,k) + phiOld(i-1,j,k)) / (dx[0]*dx[0]) +(phiOld(i,j+1,k) - 2.*phiOld(i,j,k) + phiOld(i,j-1,k)) / (dx[1]*dx[1]) +(phiOld(i,j,k+1) - 2.*phiOld(i,j,k) + phiOld(i,j,k-1)) / (dx[2]*dx[2]) ); }); // end ParallelFor The rest of the code in the main time evolution loop updates the time and :code:`MultiFab` data, prints a status update to terminal, and writes output to a plot file that will be used for visualization. Visualizing Output ~~~~~~~~~~~~~~~~~~ Data Files ^^^^^^^^^^ In :code:`main.cpp` we called a plot function in two places. The first time was to plot initial data. .. code-block:: 129 if (plot_int > 0) 130 { 131 int step = 0; 132 const std::string& pltfile = amrex::Concatenate("plt",step,5); 133 WriteSingleLevelPlotfile(pltfile, phi_old, {"phi"}, geom, time, 0); 134 } The second time plots were generated at given intervals during the main time progression loop. .. code-block:: 171 if (plot_int > 0 && step%plot_int == 0) 172 { 173 const std::string& pltfile = amrex::Concatenate("plt",step,5); 174 WriteSingleLevelPlotfile(pltfile, phi_new, {"phi"}, geom, time, step); 175 } Each time we run the code it will create a series of directories which contain data for visualization. Now run :code:`HeatEquation_Simple` with the :code:`inputs` file. After it finishes your directory should look like this. .. image:: ./images_tutorial/plot_dirs.png Visualization in Jupyter ^^^^^^^^^^^^^^^^^^^^^^^^ We will use Python and the yt package in a Jupyter notebook to generate plots for the data in the directories created in the previous step. First launch the Jupyter notebook with the command: .. code-block:: jupyter notebook When Jupyter starts, it will generate a token at the command line and ask for a password in the window it opened. Copy the token to enter to the notebook. .. image:: ./images_tutorial/token_hl.png Once the notebook starts, find :code:`Visualization.ipynb` and open it. In this file there are additional notes about the heat equation example, followed by several cells that use :code:`yt` commands to read AMReX output files. yt ^^ The following commands import the :code:`yt` package and plot a 2D slice of the output at from the 1000th time step. .. code-block:: import yt from yt.frontends.boxlib.data_structures import AMReXDataset ds = AMReXDataset("plt01000") sl = yt.SlicePlot(ds, 2, ('boxlib', 'phi')) sl In our example, the commands are already written in the notebook. To run them, select from the menu: `Kernel -> Restart & Run All`. Once the run is complete, you will get the following plot. .. image:: ./images_tutorial/heat_eq_plot.png What's Next? ~~~~~~~~~~~~ The code in this example was simplified down to a single file. Other convenient features that require more complex syntax were removed for the sake of a straight-forward presentation. In the next example we'll put these pieces back and write code like an AMReX developer.