OpenMDAO Docs Style Guide#

This document outlines OpenMDAO-v3 documentation conventions regarding both content and formatting.

General Docstring Conventions#

General docstring rules:

  • All docstrings should begin and end with triple double quotes (“””).

  • Modules, classes, methods, and functions must have docstrings whether the object is public or private.

Two types of docstrings:

  1. One-line docstrings:

     """Do something."""
  • Phrase or sentence ended by a period.

  • No empty space between the text and the triple double quotes.

  1. Multi-line docstrings:

     """Summary line.

     Paragraph 1.
     """
  • Summary line ended by a period.

  • No empty space between the summary line and the opening triple double quotes.

  • Paragraphs separated by blank lines.

  • Can contain a list of attributes/args/returns, explained below.

  • No empty line at the end, before closing triple double quotes.

Detailed docstring rules:

  1. Modules:

    • Either one-line or multi-line.

    • No blank line after the docstring.

    • List the classes and functions inside (this can be automated).

  2. Classes:

    • Either one-line or multi-line.

    • List the attributes, if any (then must be multi-line).

    • Blank line after the docstring.

     """Summary line.

     Paragraph 1.

     Attributes
     ----------
     attribute_name : Type
         description ending with a period.
     """
  1. Methods or functions:

    • Either one-line or multi-line.

    • List the arguments (except for self) and the returned variables, if any.

    • The summary line/one-line docstring should be an imperative sentence, not a descriptive phrase:

      • Incorrect: """Does something."""

      • Correct: """Do something."""

    • No blank line after the docstring.

     """Do something.

     Paragraph 1.

     Parameters
     ----------
     argument_name : Type
         description ending with a period.

     Returns
     -------
     Type
         description ending with a period.
     """
  • Sphinx does not correctly handle decorated methods. To ensure a method’s call signature appears correctly in the docs, put the call signature of the method into the first line of the docstring. See Sphinx and Decorated Methods for more information.) For example:

     """
     method_name(self, arg1, arg2)
     Do something.

     Paragraph 1.

     Parameters
     ----------
     argument_name : Type
         description ending with a period.

     Returns
     -------
     Type
         description ending with a period.
     """

Notebook Guidelines#

  1. Each notebook should include this block at the top to import OpenMDAO if not already available. This is necessary on the cloude-based notebook environments like colab.

try:
    import openmdao.api as om
except ImportError:
    !python -m pip install openmdao[notebooks]
    import openmdao.api as om

This cell should be tagged with the following metadata. The “remove-input” and “remove-output” tags prevent it from showing up in the documentation, and the “hide_input” portion collapses the input cell. To add tags in Jupyter Notebook, navigate to View -> Cell Toolbar -> Tags.

{
  "hide_input": true,
  "tags": [
    "remove-input",
    "remove-output"
  ],
  "trusted": true
}

2. Executed code in notebooks should be tested using the same assertions used in unittests.

For instance, in the paraboloid case we have:

# This code block is hidden by default.
# It exists to verify that the above code works correctly.

from openmdao.utils.assert_utils import assert_near_equal

# minimum value
assert_near_equal(prob.get_val('paraboloid.f'), -27.33333, 1e-6);

# location of the minimum
assert_near_equal(prob.get_val('paraboloid.x'), 6.6667, 1e-4);
assert_near_equal(prob.get_val('paraboloid.y'), -7.33333, 1e-4);

It’s not necessary to show this in the documentation, so remove it using the same hiding metadata above.

  1. We will use a Github Action to check all output cells from the notebooks when committing them. This will prevent git from picking up on meaningless diffs in the output cells and their metadata.

  2. Since ‘n2.html’ files and other build artifacts need to be manually copied over to the output _build directory to make the docs, each example notebook should be kept in its own directory.

Notebook Guidelines for MPI Features#

  1. Each notebook should include this block at the top to import OpenMDAO if not already available. This includes some directives that are required to interact with an mpi server that will be launched.

%matplotlib inline
from ipyparallel import Client, error
cluster=Client(profile="mpi")
view=cluster[:]
view.block=True

try:
    import openmdao.api as om
except ImportError:
    !python -m pip install openmdao[notebooks]
    import openmdao.api as om

This cell should be tagged with the following metadata. The “remove-input” and “remove-output” tags prevent it from showing up in the documentation, and the “hide_input” portion collapses the input cell. To add tags in Jupyter Notebook, navigate to View -> Cell Toolbar -> Tags.

{
  "hide_input": true,
  "tags": [
    "remove-input",
    "remove-output"
  ],
  "trusted": true
}
  1. Any cell that needs to run in MPI should start with the following line.

%%px
  1. The following steps are required in order to test and run the mpi examples. These are in addition to the normal requirements for setting up OpenMDAO under MPI.

The following only need to be done once:

pip install ipyparallel

jupyter serverextension enable --py ipyparallel

ipython profile create --parallel --profile=mpi

You will also need to edit the file ~/.ipython/profile_mpi/ipcluster_config.py and add the following line to the end:

c.IPClusterEngines.engine_launcher_class = 'MPIEngineSetLauncher'

Finally, you will need to launch an MPI server that the notebooks can use. If you cycle power on your computer, or you kill this process, then you will need to launch it again.

ipcluster start -n 2 --profile=mpi &

Embedding Autodocumentation Snippets into Documentation#

Sometimes in a feature doc, you want to reproduce a particular method or class or module right there within the text. The syntax to do this is provided by the sphinx.ext.autodoc module, in three commands, automodule, autoclass, and automethod. The syntax of these, inside of a markdown file or Jupyter cell is detailed in the following example code:

The :noindex: argument is needed to prevent unwanted replication interactions with the OpenMDAO source documentation. The above syntax will pull docstring info and produce the following output:

Custom Functions for Embedding Items into OpenMDAO Documentation#

display_source#

om.display_source is a custom function from OpenMDAO that takes one argument, which is a class, test, or method’s full, dotted path (e.g. “openmdao.core.tests.test_expl_comp.RectangleComp”).

The syntax for invoking the function within a .md or ipynb file looks like this:

import openmdao.api as om
om.display_source("openmdao.core.tests.test_expl_comp.RectangleComp")
class RectangleComp(om.ExplicitComponent):
    """
    A simple Explicit Component that computes the area of a rectangle.
    """

    def setup(self):
        self.add_input('length', val=1.)
        self.add_input('width', val=1.)
        self.add_output('area', val=1.)

    def setup_partials(self):
        self.declare_partials('*', '*')

    def compute(self, inputs, outputs):
        outputs['area'] = inputs['length'] * inputs['width']

Note

When using this function in a doc, don’t forget to apply remove-input tag to the cell for cleanliness

Embedding in this fashion has the benefit of allowing you to drop entire code blocks into a feature doc that may, for example, illustrate a usage example. Another great benefit of this method is that now your embedded example changes along with the code, so the docs maintain themselves.

By default, docstrings will be included. There is an option to the directive to strip the docstrings:

om.display_source("openmdao.core.tests.test_expl_comp.RectangleComp", hide_doc_string=True)
class RectangleComp(om.ExplicitComponent):
    

    def setup(self):
        self.add_input('length', val=1.)
        self.add_input('width', val=1.)
        self.add_output('area', val=1.)

    def setup_partials(self):
        self.declare_partials('*', '*')

    def compute(self, inputs, outputs):
        outputs['area'] = inputs['length'] * inputs['width']

Embedding More Than Just Code#

Sometimes developers will want to embed code, code output, or even plots into a document. Because our docs use Jupyter Notebooks, developers are now able to embed code examples directly into the documents just like this notebook and even write tests for the examples. Below is an example of the flexibility this allows:

# build the model
prob = om.Problem()

prob.model.add_subsystem('paraboloid', om.ExecComp('f = (x-3)**2 + x*y + (y+4)**2 - 3'))

# setup the optimization
prob.driver = om.ScipyOptimizeDriver()
prob.driver.options['optimizer'] = 'SLSQP'

prob.model.add_design_var('paraboloid.x', lower=-50, upper=50)
prob.model.add_design_var('paraboloid.y', lower=-50, upper=50)
prob.model.add_objective('paraboloid.f')

prob.setup()

# Set initial values.
prob.set_val('paraboloid.x', 3.0)
prob.set_val('paraboloid.y', -4.0)

# run the optimization
prob.run_driver();
Optimization terminated successfully    (Exit mode 0)
            Current function value: -27.33333333333333
            Iterations: 5
            Function evaluations: 6
            Gradient evaluations: 5
Optimization Complete
-----------------------------------
from openmdao.utils.assert_utils import assert_near_equal
assert_near_equal(prob.get_val('paraboloid.x'), 6.66666666, tolerance=1.0E-5)
assert_near_equal(prob.get_val('paraboloid.y'), -7.33333333, tolerance=1.0E-5)
assert_near_equal(prob.get_val('paraboloid.f'), -27.33333333, tolerance=1.0E-5)
1.2195109964002665e-10

When adding tests, use remove-output and remove-input to keep the docs clean while still allowing tests to be run when building the docs.

If you want to hide the output use the remove-output tag and/or remove-input to hide the code when the doc gets built. To add tags in Jupyter Notebook, navigate to View -> Cell Toolbar -> Tags. The output includes any output that the script produces, including plots. Below is an example of remove-output:

# build the model
prob = om.Problem()

prob.model.add_subsystem('paraboloid', om.ExecComp('f = (x-3)**2 + x*y + (y+4)**2 - 3'))

# setup the optimization
prob.driver = om.ScipyOptimizeDriver()
prob.driver.options['optimizer'] = 'SLSQP'

prob.model.add_design_var('paraboloid.x', lower=-50, upper=50)
prob.model.add_design_var('paraboloid.y', lower=-50, upper=50)
prob.model.add_objective('paraboloid.f')

prob.setup()

# Set initial values.
prob.set_val('paraboloid.x', 3.0)
prob.set_val('paraboloid.y', -4.0)

# run the optimization
prob.run_driver();

The tests will automatically detect if you have an assert in a cell but forgot to tag it with remove-output and remove-input. If you need to include an assert in a visible cell block, you can tag that block with allow-assert so that the test will skip it.

show_options_table#

om.show_options_table() is function that lets a developer display a set of options directly into a feature doc by including the module dot path name. The syntax for invoking the directive looks like this:

om.show_options_table("openmdao.solvers.linear.linear_block_jac.LinearBlockJac")

OptionDefaultAcceptable ValuesAcceptable TypesDescription
assemble_jacFalse[True, False]['bool']Activates use of assembled jacobian by this solver.
atol1e-10N/AN/Aabsolute error tolerance
err_on_non_convergeFalse[True, False]['bool']When True, AnalysisError will be raised if we don't converge.
iprint1N/A['int']whether to print output
maxiter10N/A['int']maximum number of iterations
rtol1e-10N/AN/Arelative error tolerance

Shell Commands#

If a developer wants to run a shell command, all they need to do is start the line with !. For example:

!openmdao tree ../circuit.py
/usr/share/miniconda/envs/test/lib/python3.11/pty.py:89: RuntimeWarning: os.fork() was called. os.fork() is incompatible with multithreaded code, and JAX is multithreaded, so this will likely lead to a deadlock.
  pid, fd = os.forkpty()
Driver: Driver
    Group 
        IndepVarComp ground
        IndepVarComp source
        Circuit circuit  LN: DirectSolver  NL: NewtonSolver
            Node n1
            Node n2
            Resistor R1
            Resistor R2
            Diode D1

Citations#

To insert a citation, use om.cite(). The single argument is the module dot path or the name of a function that returns an instance of the desired class when called with no arguments.

om.cite("openmdao.drivers.scipy_optimizer.ScipyOptimizeDriver")
@article{Hwang_maud_2018
 author = {Hwang, John T. and Martins, Joaquim R.R.A.},
 title = "{A Computational Architecture for Coupling Heterogeneous
          Numerical Models and Computing Coupled Derivatives}",
 journal = "{ACM Trans. Math. Softw.}",
 volume = {44},
 number = {4},
 month = jun,
 year = {2018},
 pages = {37:1--37:39},
 articleno = {37},
 numpages = {39},
 doi = {10.1145/3182393},
 publisher = {ACM},

N2#

To embed an N2 diagram simply call om.n2() inside of a Jupyter code cell and the output will be automatically formatted and displayed when given a case file or om.Problem(). In the example below, we will display the n2 from the paraboloid example from above.

om.n2(prob)