Documentation style guide ========================= This is the single document that any contributor reads before writing or editing a docstring, an RST page, or a tutorial. Phase 0 of the :doc:`commercial-release documentation overhaul ` (issue `#583 `_) makes it the authoritative bar; every Phase 1+ change is reviewed against the rules below. Anything not specified here defaults to the `PyVista documentation conventions `_, which this guide is closely modelled on. Why a style guide ----------------- femorph-solver targets a paying-customer audience that is comparing us against Ansys / Abaqus / COMSOL doc sets. Inconsistent voice, half-written docstrings, or "Examples" blocks that don't actually run all read the same way to a reader: *not ready*. The rules below keep the surface uniform regardless of which contributor authored a given page. Docstring format ---------------- Every public callable uses **NumPy-style docstrings** (the format parsed by ``numpydoc``), with the section ordering and headings shown in the canonical template: .. code-block:: python def something(arg, *, opt=None): """One-line summary, ending with a period. Longer description with the *why*, not just the *what*. Cross-link related concepts with ``:class:``, ``:meth:``, ``:func:``, ``:term:``, and ``:ref:`` roles. Parameters ---------- arg : type Plain-English description of role and units. opt : type, optional Default ``None``. What it does when set. Returns ------- type What the caller gets and what shape / units. See Also -------- related_function : one-line pointer. Notes ----- Hidden constraints, numerical-stability remarks, references to the theory page that derives the formula. References ---------- .. [1] Author, *Title*, Publisher, Year, §section. Examples -------- Plain-English lead-in, one sentence. >>> import femorph_solver as fs >>> model = fs.examples.cyclic_bladed_rotor_sector() >>> model.something(arg, opt=value) # doctest: +ELLIPSIS ... """ The summary line is **mandatory** for every public callable. The remaining sections are required only when the signature warrants them — see :ref:`section-taxonomy` below. .. _section-taxonomy: Section taxonomy by callable type --------------------------------- .. list-table:: :header-rows: 1 :widths: 22 13 13 13 13 13 13 * - Callable type - Summary - Parameters - Returns - Raises - Examples - See Also * - Public function - required - required - required - if any - **required** - encouraged * - Public method - required - required - required - if any - **required** - encouraged * - ``__init__`` - required - required - n/a - if any - on the *class* - n/a * - Public property - required - n/a - **required** (as a *Returns* block, not a *Yields*) - if any - encouraged - encouraged * - Public class - required - n/a - n/a - n/a - **required** (one per class) - encouraged * - Enum / dataclass - required at class level - n/a - n/a - n/a - encouraged - n/a * - Private (``_name``) - encouraged - encouraged - encouraged - n/a - n/a - n/a The rule of thumb: *every public surface a user can reach without underscores has a runnable* ``Examples`` *block*. The block is what readers look at first; if it works, the page works. Examples blocks --------------- * Use the ``>>>`` doctest format. ``Examples`` blocks are collected by ``pytest --doctest-modules`` and must run on every CI build (see :doc:`CI gates `). * Lead with one sentence of plain English explaining what the block demonstrates. Then the code. * Prefer the bundled fixtures in ``femorph_solver.examples`` over re-building meshes inline, so the doctest stays cheap and self-contained: * ``cantilever_hex8`` — 40-cell clamped HEX8 cantilever, pre-clamped, 0.3 s to a six-mode modal. The default fixture for ``Examples`` blocks across the public API. * ``quarter_arc_sector`` — annular HEX20 sector for sector / cylindrical-coord demos. * ``cyclic_bladed_rotor_sector`` — full bladed-rotor base sector for cyclic-symmetry demos. * For floating-point output, append the ``# doctest: +ELLIPSIS`` directive and elide the digits with ``...`` rather than pinning exact decimals. Numerical drift across BLAS / OS / threading modes will otherwise break the doctest on different runners. * Never reach the network or write outside ``tmp_path`` from a doctest. If a method writes to disk, use the standard library ``tempfile`` pattern shown in the canonical template. A worked example, for ``Model.solve_modal``: .. code-block:: python """Solve the generalised eigenproblem :math:`K\\,\\varphi = \\omega^2 M\\,\\varphi`. ... Examples -------- Run a modal solve on the bundled cantilever and print the first six natural frequencies. >>> import femorph_solver as fs >>> model = fs.examples.cyclic_bladed_rotor_sector() >>> result = model.solve_modal(n_modes=6) >>> result.frequency.shape (6,) >>> float(result.frequency[0]) > 0.0 True """ Voice and tone -------------- * **Present tense, technical, no marketing language.** We are documenting what the code does today, not pitching what it could do tomorrow. * **Explain the** *why*, **not just the** *what*. The signature already shows the *what*; the prose carries the why. "*Returns the lowest n_modes natural frequencies and the corresponding mass-normalised mode shapes — mass normalisation lets the user superpose modes without re-orthogonalising*" is a useful description. "*Returns the lowest n_modes natural frequencies*" is a re-statement of the signature. * **Plain past tense in change-log entries; present tense everywhere else.** Voice is consistent inside a page even if a reader skims many in sequence. * **No emoji, no exclamation points, no "we recommend" hedging.** Engineering documentation states behaviour and constraints; if there's a recommendation, give the reason and let the reader decide. * **Comments in code blocks** explain the *why* only when it isn't obvious from the code itself — otherwise omit them. Same rule as the runtime source code (see ``CLAUDE.md``). Cross-reference conventions --------------------------- * :literal:`:class:` for a class — :literal:`:class:\`~femorph_solver.Model\``. * :literal:`:meth:` for a method — :literal:`:meth:\`~femorph_solver.Model.solve_modal\``. * :literal:`:func:` for a free function — :literal:`:func:\`~femorph_solver.recover.compute_nodal_stress\``. * :literal:`:term:` for a glossary entry — :literal:`:term:\`shift-invert\``. * :literal:`:ref:` for a labelled section — :literal:`:ref:\`section-taxonomy\``. * :literal:`:doc:` for a relative document path — :literal:`:doc:\`/user-guide/solving/modal\``. * The leading ``~`` shortens the rendered link to just the final attribute name. Use it everywhere except when the fully-qualified path is informative. External cross-references resolve via ``intersphinx`` for ``numpy``, ``scipy``, ``pyvista``, ``matplotlib``, and ``pymapdl``. Examples: .. code-block:: rst :class:`numpy.ndarray` :class:`pyvista.UnstructuredGrid` :class:`scipy.sparse.csr_array` :func:`scipy.sparse.linalg.eigsh` Sphinx ``-W`` strict mode treats unresolved references as errors, so a typo here fails the build immediately — that is on purpose. Math ---- * **Inline math** uses the role :literal:`:math:` — :literal:`:math:\`\\omega^2 = K / M\``. * **Displayed math** uses the directive ``.. math::`` on its own line, with a blank line above and below. * **Symbol conventions** inherit from :doc:`/reference/theory/index`. Use :math:`K, M, C` for the global stiffness / mass / damping matrices, :math:`\\varphi` for a mode shape, :math:`\\omega` for a circular frequency, :math:`\\xi` for a damping ratio. Bold lowercase for vectors (:math:`\\mathbf{u}`); upright capitals for matrices. * **No vendor symbol conventions.** MAPDL's :literal:`{F}` for load vectors is *not* the convention here — write :math:`\\mathbf{f}_{\\text{ext}}` instead. Figures ------- * **SVG** is preferred for diagrams (architecture, element topology, free-body diagrams). Vector, search-friendly, diff-friendly, and renders sharply at every zoom level. * **PNG** for screenshots (gallery output, GUI captures). * Both are checked in under ``doc/source/_static/figures//`` with the convention ``_.svg`` — e.g. ``_static/figures/elements/hex8_topology.svg``. * Reference a figure with the ``figure`` directive, never the bare ``image`` directive — captions are required for everything that isn't purely decorative: .. code-block:: rst .. figure:: /_static/figures/elements/hex8_topology.svg :alt: HEX8 reference geometry showing local node order 1–8. :width: 60% HEX8 reference element with the local 1–8 node order used by femorph-solver and by MAPDL ``SOLID185``. Vendor-citation pattern ----------------------- femorph-solver is independently developed from public FEM literature. When citing a vendor verification problem, follow the fair-use posture established in :doc:`/verification/index`: * One-line factual citations only — *e.g.* "The published target for tip deflection is :math:`\\delta = 0.0163` m (NAFEMS LE5)." * Never reproduce vendor input-deck text. When a verification case is re-authored from a published problem statement, write the deck from scratch and add the footnote :literal:`(re-authored from \\\\[VM-N\\\\] under fair use; no deck text lifted)`. * Vendor element names (``SOLID185``, ``BEAM188``, …) appear *only* in the :doc:`interop ` layer. The neutral user-guide and reference layers use the topology-first names (``HEX8``, ``BEAM2``, …). Examples gallery rules ---------------------- * The example filename pattern is :literal:`(?:example_|tutorial_)`, matched by ``sphinx_gallery_conf["filename_pattern"]``. * The first triple-quoted string in an example file is its page title and intro paragraph — write it as if it were the first thing a reader sees, because it is. * Every example must be runnable on a CI runner with no network access and no GPU. Long runs (>30 s) are gated behind :literal:`DOC_FULL_GALLERY` or :literal:`DOC_RELEASE_GALLERY` in ``conf.py``. * PyVista plotting in examples must use **off-screen mode**. The gallery driver sets ``PYVISTA_OFF_SCREEN=true`` in ``conf.py``; do not call ``Plotter.show()`` interactively from an example. Examples must never call ``Plotter.show()`` interactively from a gallery script. * Use ``femorph_solver.examples.cyclic_bladed_rotor_sector`` and friends for fixtures rather than re-building meshes inline, except when the example is *about* mesh construction. Acceptance for any new docstring -------------------------------- A new public-API docstring is "done" when: #. The summary line is one sentence ending in a period. #. ``Parameters`` lists every positional and keyword argument. #. ``Returns`` describes every return value (including its shape / units / lazy-evaluation contract for ``Result`` types). #. ``Examples`` runs under ``pytest --doctest-modules`` with no warnings or errors. #. ``numpydoc.validate`` reports zero violations from the enforced class set (see :doc:`ci-gates`). #. The cross-references resolve under ``sphinx-build -W -n``. A new RST page is "done" when: #. The page title is a single H1 underline of the same length as the title. #. Every internal cross-reference resolves. #. Every code block either uses ``.. code-block:: python`` (or ``bash``, ``rst``, …) or is a ``>>>`` doctest block. #. ``make linkcheck`` returns zero broken external links. #. The page renders with no Sphinx warnings under ``-W``. See also -------- * :doc:`provenance` — sourcing policy for numerical kernels. * :doc:`/reference/glossary` — domain-term definitions. * :doc:`/reference/theory/index` — symbol conventions.