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 cloud-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. A pre-commit hook checks that all notebook output cells are clean before committing. Run jupyter nbconvert --clear-output --inplace <notebook.ipynb> to clear outputs, or use the reset_notebook command installed with OpenMDAO’s dev tools.

  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 Metadata Reference#

Cell-level tags#

Cell tags are set in the cell metadata under "tags". In Jupyter Notebook, navigate to View -> Cell Toolbar -> Tags to add them.

Tag

Effect

remove-input

Hides the cell’s source code in the rendered docs.

remove-output

Hides all output from the cell in the rendered docs.

remove-cell

Removes the entire cell (input and output) from the rendered docs.

hide-input

Collapses the cell’s source into a toggle; output is still visible.

hide-output

Collapses the cell’s output into a toggle; source is still visible.

active-ipynb

Marks a cell as active only when running interactively (e.g. Colab install cells). The doc build skips these cells.

allow-assert

Suppresses the pre-commit check that flags visible assert statements. Use when an assert must appear in the rendered output.

output_scroll

Renders the cell output in a scrollable box.

Notebook-level metadata#

Notebook-level metadata is set in the top-level "metadata" dict of the .ipynb file (not inside any individual cell). These keys are read by the doc build system.

Key

Value

Effect

"mpi"

true

Marks the notebook as requiring MPI. The build system executes it serially (outside the parallel pool) since it internally manages its own mpiexec subprocesses via mpi_exec().

"reports"

true

Executes the notebook with OPENMDAO_REPORTS=1, enabling the OpenMDAO HTML reports system. Use this only for notebooks that explicitly demonstrate the reports feature. All other notebooks run with reports disabled to avoid overhead.

"orphan"

true

Tells Sphinx not to warn about this notebook being absent from any toctree. Used for index/landing-page notebooks that are included via other mechanisms.

Notebook Guidelines for MPI Features#

For features that require MPI, use mpi_exec() from openmdao.utils.notebook_utils to run an MPI script written via %%writefile. This function:

  • Displays a note that the feature requires MPI and may not work on Colab or Binder.

  • Echoes the command being run.

  • Captures and prints stdout.

  • Raises RuntimeError if the script exits with a non-zero return code, so that doc build failures are caught.

The typical pattern looks like this:

%%writefile mpi_script_0.py
import openmdao.api as om
# ... model setup ...
prob.run_model()
from openmdao.utils.notebook_utils import mpi_exec
mpi_exec(4, 'mpi_script_0.py')

There is no need for a separate markdown warning cell — mpi_exec emits the MPI note automatically as part of its output.

Also add "mpi": true to the notebook’s top-level metadata so the build system knows to execute it outside the parallel worker pool.

Collapsible Class Definition Blocks#

When a notebook uses a class from the OpenMDAO test suite or elsewhere, it is good practice to show the class definition in a collapsible block so users can inspect it without it cluttering the page. Use the {dropdown} directive from sphinx-design together with glue from myst_nb:

from openmdao.utils.notebook_utils import get_code
from myst_nb import glue
glue("code_src", get_code("openmdao.test_suite.components.paraboloid.Paraboloid"), display=False)
:::{{dropdown}} `Paraboloid` class definition

{{glue:}}`code_src`
:::

The glue call must be executed by the kernel, so tag it remove-input and remove-output (not remove-cell) so it runs but does not appear in the rendered docs. The {dropdown} cell renders as a collapsed block with a toggle arrow. Do not use the older {Admonition} + :class: dropdown pattern, which was specific to sphinx-book-theme and does not render as collapsible in the current theme.

Building the Documentation#

See the doc build guide for instructions on building the documentation locally.

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)
np.float64(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
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)