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:
One-line docstrings:
"""Do something."""
Phrase or sentence ended by a period.
No empty space between the text and the triple double quotes.
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:
Modules:
Either one-line or multi-line.
No blank line after the docstring.
List the classes and functions inside (this can be automated).
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.
"""
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#
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.
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.
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#
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 # noqa: F401
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
}
Any cell that needs to run in MPI should start with the following line.
%%px
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:
Adding a Link to a Document in a .ipynb
File#
Sometimes in a document, you don’t want or need to embed/display the entire document of a class to make your point. At these times, you want to just provide the user with an easy way to link to the autodoc for quick reference.
We’ll do this with a []()
link. The basic syntax looks like this:
You might find this [file](openmdao_book/path/to/file.ipynb) helpful.
This could be a link to a file or a link to a website like our home page
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")
Option | Default | Acceptable Values | Acceptable Types | Description |
---|---|---|---|---|
assemble_jac | False | [True, False] | ['bool'] | Activates use of assembled jacobian by this solver. |
atol | 1e-10 | N/A | N/A | absolute error tolerance |
err_on_non_converge | False | [True, False] | ['bool'] | When True, AnalysisError will be raised if we don't converge. |
iprint | 1 | N/A | ['int'] | whether to print output |
maxiter | 10 | N/A | ['int'] | maximum number of iterations |
rtol | 1e-10 | N/A | N/A | relative 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)