From a6ed382552ff99128a1e500ba5f147b8b59956a3 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Thu, 3 Sep 2026 05:39:13 -0700 Subject: [PATCH 1/5] Add numpydoc to the docs build dependencies --- environments/building_docs.yml | 1 + pyproject.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/environments/building_docs.yml b/environments/building_docs.yml index 6afaf65c951..79407e4fdbc 100644 --- a/environments/building_docs.yml +++ b/environments/building_docs.yml @@ -6,5 +6,6 @@ dependencies: - cupy - sphinx - furo + - numpydoc - pip: - -r base_build_docs.txt diff --git a/pyproject.toml b/pyproject.toml index 0cf3e972187..a9519d1744a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,6 +89,7 @@ docs = [ "Cython", "cupy", "furo", + "numpydoc", "sphinx", "sphinx-copybutton", "sphinx-design", From ac9731fb67fe78712bd02a032bdc3091a5ad2345 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Thu, 3 Sep 2026 05:40:28 -0700 Subject: [PATCH 2/5] Switch API docs from napoleon to numpydoc --- doc/conf.py | 122 ++++++++++++++++++++++++++-------------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 0c31bc413ca..eaa6f7ce407 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -8,8 +8,10 @@ from datetime import datetime from urllib.parse import urljoin +from jinja2.sandbox import SandboxedEnvironment +from numpydoc.docscrape import NumpyDocString +from numpydoc.docscrape_sphinx import SphinxDocString from sphinx.ext.autodoc import FunctionDocumenter -from sphinx.ext.napoleon import NumpyDocstring, docstring from dpnp.dpnp_algo.dpnp_elementwise_common import ( DPNPBinaryFunc, @@ -64,7 +66,7 @@ "sphinx.ext.viewcode", "sphinx.ext.githubpages", "sphinx.ext.intersphinx", - "sphinx.ext.napoleon", + "numpydoc", "sphinx.ext.autodoc", "sphinx.ext.autosummary", "sphinx_copybutton", @@ -238,66 +240,64 @@ def _can_document_member(member, *args, **kwargs): # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True -# Napoleon settings -napoleon_use_ivar = True -napoleon_include_special_with_doc = True -napoleon_custom_sections = ["limitations"] - - -# Napoleon extension can't properly render "Returns" section in case of -# namedtuple as a return type. That patch proposes to extend the parse logic -# which allows text in a header of "Returns" section. -def _parse_returns_section_patched(self, section: str) -> list[str]: - fields = self._consume_returns_section() - multi = len(fields) > 1 - use_rtype = False if multi else self._config.napoleon_use_rtype - lines: list[str] = [] - header: list[str] = [] - is_logged_header = False - - for _name, _type, _desc in fields: - # self._consume_returns_section() stores the header block - # into `_type` argument, while `_name` has to be empty string and - # `_desc` has to be empty list of strings - if _name == "" and (not _desc or len(_desc) == 1 and _desc[0] == ""): - if not is_logged_header: - docstring.logger.info( - "parse a header block of 'Returns' section", - location=self._get_location(), - ) - is_logged_header = True - - # build a list with lines of the header block - header.extend([_type]) - continue +# Members come from autosummary; don't let numpydoc duplicate them +numpydoc_show_class_members = False + +# Keep the dpnp-only "Limitations" section (numpydoc drops unknown sections): +# register it and give it a slot in the template below +NumpyDocString.sections.setdefault("Limitations", []) + +_NUMPYDOC_TEMPLATE = """\ +{{index}} +{{summary}} +{{extended_summary}} +{{parameters}} +{{attributes}} +{{methods}} +{{returns}} +{{yields}} +{{receives}} +{{other_parameters}} +{{raises}} +{{warns}} +{{warnings}} +{{limitations}} +{{see_also}} +{{notes}} +{{references}} +{{examples}} +""" + +_orig_load_config = SphinxDocString.load_config + + +def _load_config_with_limitations(self, config): + _orig_load_config(self, config) + # Use our template with the "limitations" slot + self.template = SandboxedEnvironment().from_string(_NUMPYDOC_TEMPLATE) + + +SphinxDocString.load_config = _load_config_with_limitations + +_orig_str = SphinxDocString.__str__ + + +def _str_with_limitations(self, indent=0, func_role="obj"): + # Wrap render() to fill the "limitations" slot (a rubric, like "Notes") + orig_render = self.template.render + + def render(**ns): + ns["limitations"] = "\n".join(self._str_section("Limitations")) + return orig_render(**ns) + + self.template.render = render + try: + return _orig_str(self, indent=indent, func_role=func_role) + finally: + self.template.render = orig_render + - if use_rtype: - field = self._format_field(_name, "", _desc) - else: - field = self._format_field(_name, _type, _desc) - - if multi: - if lines: - lines.extend(self._format_block(" * ", field)) - else: - if header: - # add the header block + the 1st parameter stored in `field` - lines.extend([":returns:", ""]) - lines.extend(self._format_block(" " * 4, header)) - lines.extend(self._format_block(" * ", field)) - else: - lines.extend(self._format_block(":returns: * ", field)) - else: - if any(field): # only add :returns: if there's something to say - lines.extend(self._format_block(":returns: ", field)) - if _type and use_rtype: - lines.extend([f":rtype: {_type}", ""]) - if lines and lines[-1]: - lines.append("") - return lines - - -NumpyDocstring._parse_returns_section = _parse_returns_section_patched +SphinxDocString.__str__ = _str_with_limitations # TODO: Remove once dpnp.tensor docs are generated in dpnp From 3d2db920be521e7ae1f555df6920d408fd5ba41b Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Thu, 3 Sep 2026 05:41:45 -0700 Subject: [PATCH 3/5] Remove unnecessary dpnp-custom.js --- doc/_static/dpnp-custom.js | 67 -------------------------------------- 1 file changed, 67 deletions(-) delete mode 100644 doc/_static/dpnp-custom.js diff --git a/doc/_static/dpnp-custom.js b/doc/_static/dpnp-custom.js deleted file mode 100644 index b40ef036503..00000000000 --- a/doc/_static/dpnp-custom.js +++ /dev/null @@ -1,67 +0,0 @@ -(function() { -var separators = [ " – ", " -- " ]; - -function findSeparator(container) -{ - var walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT); - var node; - while ((node = walker.nextNode())) { - for (var i = 0; i < separators.length; i++) { - var idx = node.nodeValue.indexOf(separators[i]); - if (idx !== -1) - return {node : node, offset : idx, sep : separators[i]}; - } - } - return null; -} - -// Splits

at the separator; wraps everything after it in .param-desc. -function reformatP(p) -{ - var found = findSeparator(p); - if (!found) - return null; - - var afterNode = found.node.splitText(found.offset); - afterNode.nodeValue = afterNode.nodeValue.slice(found.sep.length); - - var range = document.createRange(); - range.setStartBefore(afterNode); - range.setEndAfter(p.lastChild); - - var desc = document.createElement("span"); - desc.className = "param-desc"; - desc.appendChild(range.extractContents()); - p.appendChild(desc); - return desc; -} - -// Browsers auto-close nested

tags, so multi-paragraph descriptions -// arrive as sibling

elements inside

  • . Fold them into the same desc. -function reformatEntry(container) -{ - var firstP = container.querySelector(":scope > p"); - if (!firstP) - return; - - var desc = reformatP(firstP); - if (!desc) - return; - - var sibling; - while ((sibling = firstP.nextElementSibling) && sibling.tagName === "P") { - if (desc.textContent.trim()) - desc.appendChild(document.createElement("br")); - while (sibling.firstChild) - desc.appendChild(sibling.firstChild); - sibling.remove(); - } -} - -document.querySelectorAll("dl.field-list dd ul.simple li") - .forEach(reformatEntry); -document.querySelectorAll("dl.field-list dd").forEach(function(dd) { - if (!dd.querySelector("ul.simple")) - reformatEntry(dd); -}); -}()); From fb0181a182830621eb49a63fcd37d1dcde0e229d Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Thu, 3 Sep 2026 05:42:22 -0700 Subject: [PATCH 4/5] Remove CSS rules made redundant by numpydoc --- doc/_static/dpnp-custom.css | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/doc/_static/dpnp-custom.css b/doc/_static/dpnp-custom.css index 6c4495a213b..c13401833bd 100644 --- a/doc/_static/dpnp-custom.css +++ b/doc/_static/dpnp-custom.css @@ -56,15 +56,3 @@ dt.sig.sig-object .sig-param > .n, dt.sig.sig-object .sig-param > .n * { font-weight: 700 !important; } - -/* Parameter/return descriptions: indented block on new line (via custom.js) */ -dl.field-list dd .param-desc { - display: block; - padding-left: 1.5em; -} - -/* Parameter lists: no bullets, keep indentation */ -dl.field-list dd ul.simple { - list-style: none !important; - padding-left: 1.2em !important; -} From bef64de345081deb083d39dd2f63a9b11b6055c9 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Thu, 3 Sep 2026 05:45:23 -0700 Subject: [PATCH 5/5] Fix dpnp docstrings for the numpydoc switch --- dpnp/dpnp_array.py | 4 ++-- dpnp/dpnp_iface_linearalgebra.py | 2 +- dpnp/dpnp_iface_manipulation.py | 2 +- dpnp/dpnp_iface_mathematical.py | 6 +++--- dpnp/dpnp_iface_nanfunctions.py | 4 ---- dpnp/dpnp_iface_trigonometric.py | 12 ++++++------ dpnp/fft/dpnp_iface_fft.py | 4 ++-- dpnp/linalg/dpnp_iface_linalg.py | 8 ++++---- dpnp/linalg/dpnp_utils_linalg.py | 2 +- 9 files changed, 20 insertions(+), 24 deletions(-) diff --git a/dpnp/dpnp_array.py b/dpnp/dpnp_array.py index 86055a4828f..d7390a1f7d4 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -1986,8 +1986,8 @@ def sort( :obj:`dpnp.searchsorted` : Find elements in a sorted array. :obj:`dpnp.partition` : Partial sort. - Note - ---- + Notes + ----- `axis` in :obj:`dpnp.sort` could be integer or ``None``. If ``None``, the array is flattened before sorting. However, `axis` in :obj:`dpnp.ndarray.sort` can only be integer since it sorts an array diff --git a/dpnp/dpnp_iface_linearalgebra.py b/dpnp/dpnp_iface_linearalgebra.py index b8c01bdc854..1d084384763 100644 --- a/dpnp/dpnp_iface_linearalgebra.py +++ b/dpnp/dpnp_iface_linearalgebra.py @@ -257,7 +257,7 @@ def einsum( The calculation based on the Einstein summation convention. See Also - ------- + -------- :obj:`dpnp.einsum_path` : Evaluates the lowest cost contraction order for an einsum expression. :obj:`dpnp.dot` : Returns the dot product of two arrays. diff --git a/dpnp/dpnp_iface_manipulation.py b/dpnp/dpnp_iface_manipulation.py index 8d3050da4e0..1ebe0880b50 100644 --- a/dpnp/dpnp_iface_manipulation.py +++ b/dpnp/dpnp_iface_manipulation.py @@ -749,7 +749,7 @@ def asarray_chkfinite( already an ndarray. Raises - ------- + ------ ValueError Raises ``ValueError`` if `a` contains NaN (Not a Number) or Inf (Infinity). diff --git a/dpnp/dpnp_iface_mathematical.py b/dpnp/dpnp_iface_mathematical.py index c66d827faff..7b7ffba9dbf 100644 --- a/dpnp/dpnp_iface_mathematical.py +++ b/dpnp/dpnp_iface_mathematical.py @@ -2329,7 +2329,7 @@ def ediff1d(ary, to_end=None, to_begin=None): returned array is determined by the Type Promotion Rules. Limitations ----------- +----------- Parameters `where` and `subok` are supported with their default values. Keyword argument `kwargs` is currently unsupported. Otherwise ``NotImplementedError`` exception will be raised. @@ -3929,8 +3929,8 @@ def _check_nan_inf(val, val_dt): :obj:`dpnp.negative` : Return the numerical negative of each element of `x`. :obj:`dpnp.copysign` : Change the sign of `x1` to that of `x2`, element-wise. -Note ----- +Notes +----- Equivalent to `x.copy()`, but only defined for types that support arithmetic. Examples diff --git a/dpnp/dpnp_iface_nanfunctions.py b/dpnp/dpnp_iface_nanfunctions.py index 10fffb34230..b528316b485 100644 --- a/dpnp/dpnp_iface_nanfunctions.py +++ b/dpnp/dpnp_iface_nanfunctions.py @@ -129,8 +129,6 @@ def nanargmax(a, axis=None, out=None, *, keepdims=False): the user is recommended to filter NaNs themselves and use `dpnp.argmax` on the filtered array. - Warnings - -------- The results cannot be trusted if a slice contains only NaNs and -Infs. @@ -213,8 +211,6 @@ def nanargmin(a, axis=None, out=None, *, keepdims=False): the user is recommended to filter NaNs themselves and use `dpnp.argmax` on the filtered array. - Warnings - -------- The results cannot be trusted if a slice contains only NaNs and -Infs. diff --git a/dpnp/dpnp_iface_trigonometric.py b/dpnp/dpnp_iface_trigonometric.py index ee3462ab610..b82f090de47 100644 --- a/dpnp/dpnp_iface_trigonometric.py +++ b/dpnp/dpnp_iface_trigonometric.py @@ -907,8 +907,8 @@ def cumlogsumexp( :obj:`dpnp.logsumexp` : Logarithm of the sum of elements of the inputs, element-wise. - Note - ---- + Notes + ----- This function is equivalent of `numpy.logaddexp.accumulate`. Examples @@ -1889,8 +1889,8 @@ def logsumexp(x, /, *, axis=None, dtype=None, keepdims=False, out=None): :obj:`dpnp.cumlogsumexp` : Cumulative the natural logarithm of the sum of elements in the input array. - Note - ---- + Notes + ----- This function is equivalent of `numpy.logaddexp.reduce`. Examples @@ -2171,8 +2171,8 @@ def reduce_hypot(x, /, *, axis=None, dtype=None, keepdims=False, out=None): -------- :obj:`dpnp.hypot` : Calculates :math:`\sqrt{x1^2 + x2^2}`, element-wise. - Note - ---- + Notes + ----- This function is equivalent of `numpy.hypot.reduce`. Examples diff --git a/dpnp/fft/dpnp_iface_fft.py b/dpnp/fft/dpnp_iface_fft.py index 90e1a112bda..0a5c3cc4351 100644 --- a/dpnp/fft/dpnp_iface_fft.py +++ b/dpnp/fft/dpnp_iface_fft.py @@ -559,7 +559,7 @@ def hfft(a, n=None, axis=-1, norm=None, out=None): -------- :obj:`dpnp.fft` : For definition of the DFT and conventions used. :obj:`dpnp.fft.rfft` : The one-dimensional FFT of real input. - :obj:`dpnp.fft.ihfft` :The inverse of :obj:`dpnp.fft.hfft`. + :obj:`dpnp.fft.ihfft` : The inverse of :obj:`dpnp.fft.hfft`. Notes @@ -1101,7 +1101,7 @@ def irfft(a, n=None, axis=-1, norm=None, out=None): :obj:`dpnp.fft.rfft` : The one-dimensional FFT of real input, of which :obj:`dpnp.fft.irfft` is inverse. :obj:`dpnp.fft.fft` : The one-dimensional FFT of general (complex) input. - :obj:`dpnp.fft.irfft2` :The inverse of the two-dimensional FFT of + :obj:`dpnp.fft.irfft2` : The inverse of the two-dimensional FFT of real input. :obj:`dpnp.fft.irfftn` : The inverse of the *N*-dimensional FFT of real input. diff --git a/dpnp/linalg/dpnp_iface_linalg.py b/dpnp/linalg/dpnp_iface_linalg.py index 76910692ea0..bbde32f36ce 100644 --- a/dpnp/linalg/dpnp_iface_linalg.py +++ b/dpnp/linalg/dpnp_iface_linalg.py @@ -481,8 +481,8 @@ def eig(a): ``eigenvectors[:,i]`` is the eigenvector corresponding to the eigenvalue ``eigenvalues[i]``. - Note - ---- + Notes + ----- Since there is no proper OneMKL LAPACK function, DPNP will calculate through a fallback on NumPy call. @@ -645,8 +645,8 @@ def eigvals(a): They are not necessarily ordered, nor are they necessarily real for real matrices. - Note - ---- + Notes + ----- Since there is no proper OneMKL LAPACK function, DPNP will calculate through a fallback on NumPy call. diff --git a/dpnp/linalg/dpnp_utils_linalg.py b/dpnp/linalg/dpnp_utils_linalg.py index 527235496b9..0326063978e 100644 --- a/dpnp/linalg/dpnp_utils_linalg.py +++ b/dpnp/linalg/dpnp_utils_linalg.py @@ -736,7 +736,7 @@ def _calculate_determinant_sign(ipiv, diag, res_type, n): values. Parameters - ----------- + ---------- ipiv : {dpnp.ndarray, usm_ndarray} The pivot indices from LU decomposition. diag : {dpnp.ndarray, usm_ndarray}