Developer Guide

Step-by-step development guide

This is a quick tutorial intended for new Alamo developers to get up to speed with contributing to the Alamo code base.

  1. Create Github credentials.
    1. If you do not have one already, create a new GitHub account, and email your username to brunnels@iastate.edu to request push access

    2. Follow these Github instructions to add an SSH key to your account.

    3. important Make sure your repository points to the SSH version of Alamo. You can check this with the command

    git remote show origin
    

    If the output begins with

    Fetch URL: git@github.com:solidsgroup/alamo.git
    Push  URL: git@github.com:solidsgroup/alamo.git
    

    then you have configured correctly check. However, if the output begins with

    Fetch URL: https://github.com/solidsuccs/alamo.git
    Push  URL: https://github.com/solidsuccs/alamo.git
    

    this means that you are using HTTPS authentication, which will not allow you to push. To fix, run

    git remote remove origin
    git remote add origin git@github.com:solidsgroup/alamo.git
    

    Run a quick git pull to make sure that you still have access. If you get an authentication error, this likely means that your SSH key is not configured correctly.

  2. Create a new branch. You can create a branch from the terminal, or you can use the Github online interface. Follow these guidlines when naming your branch

    • Use combinations of lowercase letters and hyphens, avoid mixed case, numbers, and underscores (gas-combustion good, GasCombustion2 bad).

    • Be as descriptive as possible and avoid overly general names (gb-damage-model good, model1 / mymodel / foo bad). Long is ok.

    • Keep it professional. No profanity or excessive whimsy in branch names.

    If you have created a branch (e.g. mytestbranch) locally, make sure to run

    git push --set-upstream origin mytestbranch
    

    to make pushing easier.

  3. Create a capability input file. This is an input file that is designed to demonstrate the functionality of your new code. The capability input file should always be located in the alamo root directory. The plot_file should always be output. Once you are ready to merge your code into development, convert your input file to a regression test and remove it from the root. (See the Autodoc and Autotest section).

  4. Use an EditorConfig-compliant text editor. Alamo uses EditorConfig to maintain code formatting standards. VS Code is recommended, as it supports multiple keybinding schemes and automatically supports editorconfig.

  5. Write your code. As you are writing, keep the following in mind:

    always

    Commit your code - at least once per day you are coding, even if the code is not working. There are multiple great tutorials if you are not sure how to commit your code.

    always

    Write meaningful commit messages. This will help you more than you may realize when you are trying to debug your code later.

    always

    Follow the development guide below and stick to the specified convention.

    regularly

    Merge the latest development into your code (see below for more details). The more frequently you do this, the less painful your life will be later.

    regularly

    Run the regression test suite with make test. This helps to ensure that you are not breaking your (or someone else’s) code.

    regularly

    Ensure that your commits pass the commit tests. Sometimes this is not possible, especially if your code is in active development, but the more you keep your code up to standard, the easier your life will be later.

    regularly

    Document your code. Use the autodoc system to ensure a base level of documentation.

    rarely

    Break backwards compatibility. If you are implementing an improved version of a model that is better than the legacy model, leave the option to run using the existing model. This is important because publications may have used the legacy model, and may need the legacy model to reproduce important results. Backwards compatibility should only be broken when implementing fundamental new features. Even then, changes required to reproduce legacy results should be kept minimal.

    never

    Modify the GitHub actions to get your branch to pass the tests. Email failure notifications may be annoying, but they are there for a reason.

    never

    Commit large or unnecessary files to the repository. Use git status liberally, before and after git commit, before you push. Edit your gitignore file as needed.

    never

    Commit sensitive content to the repository. This is usually an issue only if your project is export controlled, which is rare. But when in doubt, ask.

  6. Merge your branch into development To include your changes into the development branch, submit a pull request from your branch into development. This will trigger a review before the merge can be approved. (Note that all tests must pass before the merge will be reviewed.) The more frequently you merge in the latest from development, the easier your merge will be.

    How do I know my code is ready to merge? You should always merge your code once you know that it works, but sometimes this is a gray area. You should always merge your code if you are submitting a publication, or if you are a PhD student about to graduate.

Tutorial: A new Integrator

Integrators are the basic workhorse in Alamo. They are called Integrators because they usually (although not always) are intended to integrate a PDE in time. In this tutorial, we will create a new integrator based on the existing HeatConduction integrator.

  1. Create a copy of ./src/Integrator/HeatConduction.H inside the same directory, called MyPDE.H (We will use the working name “MyPDE” here - you can replace with your own name.)

  2. Rename all instances of HeatConduction with MyPDE inside ./src/Integrator/MyPDE.H. This includes the names throughout the code as well as the include guard in the first two lines.

  3. Include the file in ./src/alamo.cc

    #include "Integrator/MyPDE.H"
    

    and add a caller inside the main function:

    //existing
    else if (program == "thermoelastic")integrator = new Integrator::ThermoElastic(pp);
    //new
    else if (program == "mypde")        integrator = new Integrator::MyPDE(pp);
    
  4. Finally copy the Heat Conduction example file to the root directory

    cp ./tests/HeatConduction/input ./input
    

    In the input file change the plot_file to output and alamo.program to mypde.

  5. You should now be able to compile and run the code. The result will be the same as for the heat conduction example, but will be based on the newly copied integrator.

  6. You can now begin alternating the code to achieve different outputs. The HeatConduction integrator has extensive, line-by-line documentation.

Conventions

Namespaces

Classes and structures are organized using namespaces. Namespaces should be brief but descriptive, and two nested namespaces should correspond to two nested subsets. For instance Shape::Polygon::Triangle is acceptable whereas Shape::Circle::Round is not (since “round” is not a subset of “Circle”).

The directory structure must reflect the namespace structure. For instance Shape::Polygon::Triangle must be declared in src/Shape/Polygon/Triangle.H.

Regular and Pure Virtual classes

Pure virtual classes must have the same name as the namespace in which they are contained. For instance Model::Elastic::Elastic is pure virtual, whereas Model::Elastic::Isotropic must inherit from Model::Elastic::Elastic and is not pure virtual.

Include Guards

Include guards should be all uppercase, and should be the same as the path (relative to src/) with / replaced by underscores. For instance: a file called src/Integrator/Integrator.H should have the include guard

#ifndef INTEGRATOR_INTEGRATOR_H
#define INTEGRATOR_INTEGRATOR_H
...
#endif

Member and Argument Variable Names

Members of a class should have names beginning with m_; argument variables should begin with a_. For instance:

class MyClass
{
public:
   MyClass(a_myvariable) : m_myvariable(a_myvariable)
   {}
private:
   m_myvariable;
}

Python (In development)

Alamo supports a (currently limited) Python interface. It can be run as simply as

python alamo.py

where alamo.py is a python script that you write. An example (that currently works) is

import alamo

alamo.Util.Initialize()

mytest = alamo.Test.Operator.Elastic()

mytest.setBounds([1.0,1.0,1.0])

for lev in [1,2,3]:
    print("Levels of refinement: " + str(lev))
mytest.Define([32,32,32],lev,1,mytest.Grid.XYZ)
failed = mytest.TrigTest(0,0,1,"")
if failed: print("Test failed")
else: print ("Test passed")

alamo.Util.Finalize()

Note that the C++ namespace structure is mirrored in the Python bindings.

Warning

Note that object constructors require (), e.g. test = alamo.Test.Operator.Elastic(). If you forget the () the python interpreter will not complain! Instead, it will give you an extremely cryptic message about member methods not having the right number of arguments. A typical error message might look like this:

Traceback (most recent call last):
File "alamo.py", line 7, in <module>
mytest.setBounds([1.0,1.0,1.0])
TypeError: unbound method Boost.Python.function object must be called with Elastic instance as first argument (got list instance instead)

Compiling Alamo Python interface

The python interface requires all code to be compiled using the -fPIC flag. To compile AMReX,

./compile [whatever flags you would normally use
make USE_COMPILE_PIC=TRUE
make install

To compile Alamo, you need the Boost python library. On ubuntu, install with

sudo apt install libboost-python-dev

Then to compile Alamo,

./configure [whatever flags you would normally use] --python
make python

This will create a file called alamo.so in the Alamo root directory. (Note that this file must be in your python path (or your current directory) to use.)

Extending the Alamo Python interface

Python bindings are currently limited and you will likely need to add them if you want them for a specific component. Alamo uses the Boost Python library to create the Python bindings. All of this code is located in the py/ directory, and mimics the directory structure of src/. These are all C++ files, but use the .cpy extension. To add bindings (e.g. to an object) you must

  1. Create an appropriately named file (e.g. py/Test/Operator/Elastic.cpy to bind Test::Operator::Elastic).

  2. Define a function that encapsulates the bindings (e.g. void exportTestOperatorElastic).

  3. Write the Boost Python bindings in the function. Keep in mind that you need to specify namespace explicitly.

  4. Include the file in py/alamo.cpy

  5. Call the function in py/alamo.cpy

Restart files

You can restart a simulation from any point at which you have dumped an output file. For instance, suppose you have run alamo with this command

./bin/alamo-2d-g++ input

and it produced the following output.

./output
    00000cell 00000node
    00010cell 00010node
    00020cell 00020node
    00030cell 00030node
    00040cell 00040node
    00050cell 00050node
    00060cell 00060node
    00070cell 00070node
    celloutput.visit
    nodeoutput.visit
    metadata

You may wish to restart the simulation without starting from the beginning. Perhaps the simulation was fine at 00060 but became unstable at 00070, and you want to continue from that point with a different parameter value. To do that, you can simply run:

./bin/alamo-2d-g++ input restart=./output/00060cell

for the simulation to pick up where it left off, but with updated values (or updated code) that may have changed since you first ran it.

It is important to distinguish what the restart function does and does not do.

The restart function DOES:
  • Import all of the data from fields named “XXX” “YYY” etc into corresponding fields with the same names in the new simulation.

  • Set the clock equal to the time corresponding to the output file.

  • Set the current timestep equal to that corresponding to the output file.

The restart function DOES NOT:
  • Set any other simulation parameters. The input file is responsible for setting all simulation parameters. It is up to you to use them (or change them) as needed.

  • Initialize non-matching data. If you try to restart using data containing fields “AAA” and “BBB” but your simulation expects fields “BBB” and “CCC”, only “BBB” will be initialized - the rest is undefined.

  • Continue to output to the same directory. A different directory will be created. Only the metadata file will record where the restard data came from.