Skip to content

Commit 898b6f2

Browse files
authored
Return named tuple for linalg functions per python array API (#2276)
The PR proposes to return namedtuple for linalg functions: `qr`, `eig`, `eigh`, `svd` and `slogdet`. The namedtuple as a return type there is mandated to be compliant with python array API. The tests are updated to cover namedtyple as a return type of affected function. Also this PR requires to patch how napoleon extension from sphinx handles docstring description of namedtuple. The handling was align with NumPy approach.
1 parent e130df0 commit 898b6f2

File tree

8 files changed

+270
-109
lines changed

8 files changed

+270
-109
lines changed

.github/workflows/array-api-skips.txt

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,7 @@ array_api_tests/test_signatures.py::test_func_signature[unique_counts]
1717
array_api_tests/test_signatures.py::test_func_signature[unique_inverse]
1818
array_api_tests/test_signatures.py::test_func_signature[unique_values]
1919

20-
# do not return a namedtuple
21-
array_api_tests/test_linalg.py::test_eigh
22-
array_api_tests/test_linalg.py::test_slogdet
23-
array_api_tests/test_linalg.py::test_svd
24-
2520
# hypothesis found failures
26-
array_api_tests/test_linalg.py::test_qr
2721
array_api_tests/test_operators_and_elementwise_functions.py::test_clip
2822

2923
# unexpected result is returned - unmute when dpctl-1986 is resolved

doc/conf.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from datetime import datetime
1010

1111
from sphinx.ext.autodoc import FunctionDocumenter
12+
from sphinx.ext.napoleon import NumpyDocstring, docstring
1213

1314
from dpnp.dpnp_algo.dpnp_elementwise_common import DPNPBinaryFunc, DPNPUnaryFunc
1415

@@ -231,3 +232,59 @@ def _can_document_member(member, *args, **kwargs):
231232
napoleon_use_ivar = True
232233
napoleon_include_special_with_doc = True
233234
napoleon_custom_sections = ["limitations"]
235+
236+
237+
# Napoleon extension can't properly render "Returns" section in case of
238+
# namedtuple as a return type. That patch proposes to extend the parse logic
239+
# which allows text in a header of "Returns" section.
240+
def _parse_returns_section_patched(self, section: str) -> list[str]:
241+
fields = self._consume_returns_section()
242+
multi = len(fields) > 1
243+
use_rtype = False if multi else self._config.napoleon_use_rtype
244+
lines: list[str] = []
245+
header: list[str] = []
246+
is_logged_header = False
247+
248+
for _name, _type, _desc in fields:
249+
# self._consume_returns_section() stores the header block
250+
# into `_type` argument, while `_name` has to be empty string and
251+
# `_desc` has to be empty list of strings
252+
if _name == "" and (not _desc or len(_desc) == 1 and _desc[0] == ""):
253+
if not is_logged_header:
254+
docstring.logger.info(
255+
"parse a header block of 'Returns' section",
256+
location=self._get_location(),
257+
)
258+
is_logged_header = True
259+
260+
# build a list with lines of the header block
261+
header.extend([_type])
262+
continue
263+
264+
if use_rtype:
265+
field = self._format_field(_name, "", _desc)
266+
else:
267+
field = self._format_field(_name, _type, _desc)
268+
269+
if multi:
270+
if lines:
271+
lines.extend(self._format_block(" * ", field))
272+
else:
273+
if header:
274+
# add the header block + the 1st parameter stored in `field`
275+
lines.extend([":returns:", ""])
276+
lines.extend(self._format_block(" " * 4, header))
277+
lines.extend(self._format_block(" * ", field))
278+
else:
279+
lines.extend(self._format_block(":returns: * ", field))
280+
else:
281+
if any(field): # only add :returns: if there's something to say
282+
lines.extend(self._format_block(":returns: ", field))
283+
if _type and use_rtype:
284+
lines.extend([f":rtype: {_type}", ""])
285+
if lines and lines[-1]:
286+
lines.append("")
287+
return lines
288+
289+
290+
NumpyDocstring._parse_returns_section = _parse_returns_section_patched

dpnp/dpnp_array.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -760,12 +760,14 @@ def argsort(
760760

761761
def asnumpy(self):
762762
"""
763-
Copy content of the array into :class:`numpy.ndarray` instance of the same shape and data type.
763+
Copy content of the array into :class:`numpy.ndarray` instance of
764+
the same shape and data type.
764765
765766
Returns
766767
-------
767-
numpy.ndarray
768-
An instance of :class:`numpy.ndarray` populated with the array content.
768+
out : numpy.ndarray
769+
An instance of :class:`numpy.ndarray` populated with the array
770+
content.
769771
770772
"""
771773

dpnp/dpnp_iface_manipulation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1124,7 +1124,7 @@ def broadcast_shapes(*args):
11241124
11251125
Returns
11261126
-------
1127-
tuple
1127+
out : tuple
11281128
Broadcasted shape.
11291129
11301130
See Also

dpnp/dpnp_iface_statistics.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ def max(a, axis=None, out=None, keepdims=False, initial=None, where=True):
843843
dimension ``a.ndim - len(axis)``.
844844
845845
Limitations
846-
-----------.
846+
-----------
847847
Parameters `where`, and `initial` are only supported with their default
848848
values. Otherwise ``NotImplementedError`` exception will be raised.
849849

0 commit comments

Comments
 (0)