Code Development Guide

All of our curated code is in Julia. Only maintainers should push directly to #master. Others should work off a branch or fork. When you have parts that are ready to contribute submit a pull request so the maintainers can review/discuss it.

Main Requirements

  1. Code must be in Julia package format so someone else can easily install it and it must support the latest stable version of Julia. To create a package I recommend using PkgTemplates as it’ll automate a lot of the mundane details. Here is a typical template:
     t = Template(user="byuflowlab", dir=".", plugins=[GitHubPages()])

    Setting the user creates the correct url’s, I usually prefer a local directory for development, and the later is helpful if you are going to use Documenter.jl.

  2. Good documentation is a must. Documenter.jl is the standard format in Julia, but other formats can also work well. Try to address the four separate purposes of documentation. That usually means a “Quick Start” tutorial aimed at beginners, some how-to examples to do more advanced things, a reference guide for the API, and a theory document.

  3. Create CI tests:
    • Set up unit tests. The pkgtemplate will already have a script started for you. Generally, you should test against known solutions (e.g., analytic solutions). Often we compare floating point numbers so == is too restrictive. The isapprox function, with a specified tolerance is useful for this.
    • Create a Github Action. Use the julia-runtest action. See the test script for FLOWMath.jl, which has a few modifications (only reruns tests when .jl files are pushed, sets the versions correctly).
    • Add a badge to your README to show test status.
  4. Write generic code so as to support algorithmic differentiation. Almost everything we use ends up in an optimization at some point, and we will want to have numerically exact gradients. Check that ForwardDiff and ReverseDiff work with your code. To test, wrap your code in a function f = wrapper(x) that takes in a vector x for the inputs and returns a vector of outputs. Then call J = ForwardDiff.jacobian(wrapper, x). If it works, check it against finite differencing. These checks should go in your unit tests. Often the main fix needed is that your code is specialized for floats so the dual numbers can’t propgate:
    • instead of 0.0 as a default parameter use zero(x)
    • instead of zeros(n) or Vector{Float64}(undef, n) use zeros(typeof(x), n) or Vector{typeof(x)}(undef, n)
    • use parametric types on your structs (discussed below).
  5. Send your code to a peer (and generally me as well) for review. At a minimum they will review it as a user (can they install and understand how to use it), and if appropriate may also review as a developer (is the code clear, are the methods appropriate).

Other Guidelines