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;
-}
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);
-});
-}());
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
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}
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",