diff --git a/data b/data index f66cfdff1..632f542a7 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit f66cfdff19065c97ca49d7e2a538e9b4ab8504ff +Subproject commit 632f542a742e4cfaa62edab54affee1f5796fe19 diff --git a/docs/docs_requirements.txt b/docs/docs_requirements.txt index 0d208e1e4..1e55847bf 100644 --- a/docs/docs_requirements.txt +++ b/docs/docs_requirements.txt @@ -1,9 +1,9 @@ -r ../requirements.txt -sphinx>=4.2.0,<=5.3.0 +sphinx>=5.0.0 sphinxcontrib-napoleon sphinxcontrib-bibtex sphinx-autodoc-typehints -faculty-sphinx-theme +furo jinja2<3.1.0 # temporary fix for jinja2/nbconvert bug traitlets!=5.2.2 # temporary fix for ipython/traitlets#741 nbsphinx @@ -12,4 +12,4 @@ ipython_genutils py2jn pygraphviz>=1.7 pandoc -docutils==0.16 +docutils>=0.18 diff --git a/docs/source/_static/scico.css b/docs/source/_static/scico.css index 3bce4e93d..eed587dc6 100644 --- a/docs/source/_static/scico.css +++ b/docs/source/_static/scico.css @@ -1,47 +1,23 @@ -/* Left hand navigation bar colours */ +/* furo theme customization */ -nav.wy-nav-side, nav.wy-nav-top { - background: #323232; +.sidebar-drawer { + width: fit-content !important; } -div.wy-side-nav-search a.heading:hover { - background-color: #575757; +.main > .content { + min-width: 75%; + width: fit-content !important; + max-width: 80em; } - -/* Allow larger maximum width of main content */ - -.wy-nav-content { - max-width: 1200px !important; -} - - -/* Footer */ - -footer { - font-size: 70%; -} - -footer > div.rst-footer-buttons { - font-size: 125%; +.highlight { + background: #f0ffe8; } - -/* Fix indices and tables spacing */ - -#indices-and-tables p { - margin-bottom: 0; -} - - -/* Literal sections */ - -div.highlight-default { - border: 0 !important; -} - -div.highlight { - background-color: inherit; +.sidebar-brand-text { + font-size: 1.0rem !important; + text-align: center; + padding-top: 0.5em; } @@ -52,163 +28,78 @@ div.doctest.highlight-default { } -/* Improve references layout */ - -div.citation { - display: flex; -} - -div.citation > span.label { - float: left; - padding-right: 0.8em; - padding-top: 0.1em; -} - - -/* Todo note */ - -div.admonition-todo { - background-color: #f0c0b0 !important; -} - -div.admonition-todo > p.admonition-title { - background-color: #ff2020 !important; -} - - -/* References layout and appearance */ - -dl.citation { - display: grid; - grid-template-columns: max-content auto; -} - -dl.citation > dt { - background-color: #ffffff !important; - border-top: 0 !important; - margin: 0 !important; - grid-column-start: 1; - width: 2em; -} - -dl.citation > dd { - padding-bottom: 0 !important; - margin-bottom: 0 !important; - margin-left: 5px !important; - grid-column-start: 2; -} - - -/* Fix equation number location */ - -.math { - text-align: left; -} - -.eqno { - float: right; -} - - /* Style for autosummary API docs */ -dl.field-list.simple { +[data-theme=light] dl.field-list.simple { background-color: #f5f5f5; border-radius: 4px; } -dl.field-list.simple > dt.field-odd { +[data-theme=light] dl.field-list.simple > dt.field-odd { background-color: #f2f2f2; - border-bottom: solid 3px #c0c0c0; border-radius: 4px; } -dl.field-list.simple > dt.field-even { +[data-theme=light] dl.field-list.simple > dt.field-even { background-color: #f2f2f2; - border-bottom: solid 3px #c0c0c0; border-radius: 4px; } -dl.py.data { +[data-theme=light] dl.py.data { background-color: #fdfafa; border-radius: 4px; } -dl.py.data > dt { +[data-theme=light] dl.py.data > dt { border-radius: 4px; } -dl.py.attribute { +[data-theme=light] dl.py.attribute { background-color: #fdfafa; border-radius: 4px; } -dl.py.attribute > dt { +[data-theme=light] dl.py.attribute > dt { border-radius: 4px; } -dl.py.function { +[data-theme=light] dl.py.function { background-color: #fdfafa; border-radius: 4px; } -dl.py.function > dt { +[data-theme=light] dl.py.function > dt { border-radius: 4px; } -dl.py.class { +[data-theme=light] dl.py.function blockquote { + background-color: #f5f5f5; + border-left: 0px; +} + +[data-theme=light] dl.py.class { background-color: #fdfafa; border-radius: 4px; } -dl.py.class > dt { +[data-theme=light] dl.py.class > dt { border-radius: 4px; } -dl.py.method { +[data-theme=light] dl.py.method { background-color: #f6f6f6; border-radius: 4px; } -dl.py.method > dt { - border-top: solid 3px #c0c0c0 !important; +[data-theme=light] dl.py.method > dt { border-radius: 4px; } -dl.py.property { +[data-theme=light] dl.py.property { background-color: #f6f6f6; border-radius: 4px; } -dl.py.property > dt { - border-top: solid 3px #c0c0c0 !important; +[data-theme=light] dl.py.property > dt { border-radius: 4px; } - - -/* Temporary hack to improve autodoc class attribute layout */ - -.py.class dl:not(.py.attribute) + .py.attribute::before { - content: "Attributes"; - font-weight: bold; - font-size: 90%; - display: inline-block; - color: #555; - background-color: #f5f5f5; - padding: 4px; - margin-left: -3em; - margin-bottom: 15px; - border-radius: 4px; - border-bottom: solid 3px #c0c0c0; - border-left: solid 3px #c0c0c0; -} - -.py.class dl.py.attribute { - padding-left: 3em; -} - -.py.class dl.py.attribute > dt { - border-bottom: solid 3px #c0c0c0 !important; - border-left: solid 3px #c0c0c0 !important; - border-radius: 4px; -} diff --git a/docs/source/_templates/sidebar/brand.html b/docs/source/_templates/sidebar/brand.html new file mode 100644 index 000000000..180004504 --- /dev/null +++ b/docs/source/_templates/sidebar/brand.html @@ -0,0 +1,31 @@ +{#- + +Hi there! + +You might be interested in https://pradyunsg.me/furo/customisation/sidebar/ + +Although if you're reading this, chances are that you're either familiar +enough with Sphinx that you know what you're doing, or landed here from that +documentation page. + +Hope your day's going well. :) + +-#} + diff --git a/docs/source/advantages.rst b/docs/source/advantages.rst index 76dc1a2d5..cacc61644 100644 --- a/docs/source/advantages.rst +++ b/docs/source/advantages.rst @@ -1,30 +1,6 @@ Why SCICO? ========== - -.. raw:: html - - - - - Advantages of JAX-based Design ------------------------------ diff --git a/docs/source/api.rst b/docs/source/api.rst index 1bafc46f0..f824889d7 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -1,3 +1,5 @@ +:orphan: + API Documentation ================= diff --git a/docs/source/conf.py b/docs/source/conf.py index 229df5f06..6e8b5f38c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -73,7 +73,7 @@ def patched_parse(self): sys.path.insert(0, rootpath) # If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = "4.2.0" +needs_sphinx = "5.0.0" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -193,14 +193,18 @@ def patched_parse(self): "tmp", "*.tmp.*", "*.tmp", - "index.ipynb", - "exampledepend.rst", - "blockarray.rst", - "operator.rst", - "functional.rst", - "optimizer.rst", + "examples", + "include", ] + +# napoleon_include_init_with_doc = True +napoleon_use_ivar = True +napoleon_use_rtype = False + +# See https://github.com/sphinx-doc/sphinx/issues/9119 +# napoleon_custom_sections = [("Returns", "params_style")] + # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = False @@ -212,24 +216,26 @@ def patched_parse(self): # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -# html_theme = "sphinx_rtd_theme" -html_theme = "faculty-sphinx-theme" +# html_theme = "python_docs_theme" +html_theme = "furo" html_theme_options = { - "includehidden": False, - "logo_only": True, + # "sidebar_hide_name": True, } +if html_theme == "python_docs_theme": + html_sidebars = { + "**": ["globaltoc.html", "sourcelink.html", "searchbox.html"], + } + # The name of an image file (relative to this directory) to place at the top # of the sidebar. -# html_logo = None html_logo = "_static/logo.svg" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -# html_favicon = None html_favicon = "_static/scico.ico" # Add any paths that contain custom static files (such as style sheets) here, @@ -286,23 +292,21 @@ def patched_parse(self): # Added timeout due to periodic scipy.org down time # intersphinx_timeout = 30 -# napoleon_include_init_with_doc = True -napoleon_use_ivar = True -napoleon_use_rtype = False - -# See https://github.com/sphinx-doc/sphinx/issues/9119 -# napoleon_custom_sections = [("Returns", "params_style")] - graphviz_output_format = "svg" inheritance_graph_attrs = dict(rankdir="LR", fontsize=9, ratio="compress", bgcolor="transparent") +inheritance_edge_attrs = dict( + color='"#2962ffff"', +) inheritance_node_attrs = dict( shape="box", fontsize=9, height=0.4, margin='"0.08, 0.03"', style='"rounded,filled"', - fillcolor='"#f4f4ffff"', + color='"#2962ffff"', + fontcolor='"#2962ffff"', + fillcolor='"#f0f0f8b0"', ) @@ -336,7 +340,10 @@ def patched_parse(self): if on_rtd: print("Building on ReadTheDocs\n") - print("Current working directory: {}".format(os.path.abspath(os.curdir))) + print(" current working directory: {}".format(os.path.abspath(os.curdir))) + print(" rootpath: %s" % rootpath) + print(" confpath: %s" % confpath) + import numpy as np print("NumPy version: %s" % np.__version__) @@ -344,10 +351,6 @@ def patched_parse(self): matplotlib.use("agg") -print("Sphinx paths:") -print(f" rootpath: {rootpath}") -print(f" confpath: {confpath}") - autodoc_default_options = { "member-order": "bysource", diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst index 71f652945..9ec446cc7 100644 --- a/docs/source/contributing.rst +++ b/docs/source/contributing.rst @@ -3,34 +3,6 @@ Contributing ============ -.. raw:: html - - - - Contributions to SCICO are welcome. Before starting work, please contact the maintainers, either via email or the GitHub issue system, to discuss the relevance of your contribution and the most appropriate @@ -120,7 +92,7 @@ specific dependencies by running :: - pip install -r docs_requirements.txt + pip install -r docs/docs_requirements.txt Then, a local copy of the documentation can be built from the respository root directory by running diff --git a/docs/source/docutil.py b/docs/source/docutil.py index 46fb8dfeb..a841a2572 100644 --- a/docs/source/docutil.py +++ b/docs/source/docutil.py @@ -55,6 +55,46 @@ def package_classes(package): return classes +def get_text_indentation(text, skiplines=0): + """Compute the leading whitespace indentation in a block of text. + + Args: + text: A block of text as a string. + + Returns: + Indentation length. + """ + min_indent = len(text) + lines = text.splitlines() + if len(lines) > skiplines: + lines = lines[skiplines:] + else: + return None + for line in lines: + if len(line) > 0: + indent = len(line) - len(line.lstrip()) + if indent < min_indent: + min_indent = indent + return min_indent + + +def add_text_indentation(text, indent): + """Insert leading whitespace into a block of text. + + Args: + text: A block of text as a string. + indent: Number of leading spaces to insert on each line. + + Returns: + Text with additional indentation. + """ + lines = text.splitlines() + for n, line in enumerate(lines): + if len(line) > 0: + lines[n] = (" " * indent) + line + return "\n".join(lines) + + def insert_inheritance_diagram(clsqname, parts=None, default_nparts=2): """Insert an inheritance diagram into a class docstring. @@ -101,11 +141,14 @@ def insert_inheritance_diagram(clsqname, parts=None, default_nparts=2): # Define inheritance diagram insertion text idstr = f""" - .. inheritance-diagram:: {clsname} - :parts: {nparts} + .. inheritance-diagram:: {clsname} + :parts: {nparts} """ + docstr_indent = get_text_indentation(docstr, skiplines=1) + if docstr_indent is not None and docstr_indent > 4: + idstr = add_text_indentation(idstr, docstr_indent - 4) # Insert inheritance diagram after summary line and whitespace line following it lines.insert(2, idstr) # Construct new docstring and attach it to the class diff --git a/docs/source/include/blockarray.rst b/docs/source/include/blockarray.rst index 7cfa430f2..b7a97fdac 100644 --- a/docs/source/include/blockarray.rst +++ b/docs/source/include/blockarray.rst @@ -3,7 +3,7 @@ BlockArray ========== - .. testsetup:: +.. testsetup:: >>> import scico >>> import scico.numpy as snp @@ -20,7 +20,7 @@ that, whenever possible, :class:`.BlockArray` properties and methods map along the blocks, returning another :class:`.BlockArray` or tuple as appropriate. For example, - :: +:: >>> x = snp.blockarray(( ... [[1, 3, 7], @@ -57,52 +57,53 @@ NumPy and SciPy Functions functions have been extended to work with instances of :class:`.BlockArray`. In particular: - * When a tuple of tuples is passed as the `shape` - argument to an array creation routine, a :class:`.BlockArray` is created. - * When a :class:`.BlockArray` is passed to a reduction function, the blocks are - ravelled (i.e., reshaped to be 1D) and concatenated before the reduction - is applied. This behavior may be prevented by passing the `axis` - argument, in which case the function is mapped over the blocks. - * When one or more :class:`.BlockArray` instances are passed to a mathematical - function that is not a reduction, the function is mapped over - (corresponding) blocks. +* When a tuple of tuples is passed as the `shape` + argument to an array creation routine, a :class:`.BlockArray` is created. +* When a :class:`.BlockArray` is passed to a reduction function, the blocks are + ravelled (i.e., reshaped to be 1D) and concatenated before the reduction + is applied. This behavior may be prevented by passing the `axis` + argument, in which case the function is mapped over the blocks. +* When one or more :class:`.BlockArray` instances are passed to a mathematical + function that is not a reduction, the function is mapped over + (corresponding) blocks. For a list of array creation routines, see - :: +:: - >>> scico.numpy.creation_routines # doctest: +ELLIPSIS - ('empty', ...) + >>> scico.numpy.creation_routines # doctest: +ELLIPSIS + ('empty', ...) For a list of reduction functions, see - :: +:: - >>> scico.numpy.reduction_functions # doctest: +ELLIPSIS - ('sum', ...) + >>> scico.numpy.reduction_functions # doctest: +ELLIPSIS + ('sum', ...) For lists of the remaining wrapped functions, see - :: +:: - >>> scico.numpy.mathematical_functions # doctest: +ELLIPSIS - ('sin', ...) - >>> scico.numpy.testing_functions # doctest: +ELLIPSIS - ('testing.assert_allclose', ...) - >>> import scico.scipy - >>> scico.scipy.special.functions # doctest: +ELLIPSIS - ('betainc', ...) + >>> scico.numpy.mathematical_functions # doctest: +ELLIPSIS + ('sin', ...) + >>> scico.numpy.testing_functions # doctest: +ELLIPSIS + ('testing.assert_allclose', ...) + >>> import scico.scipy + >>> scico.scipy.special.functions # doctest: +ELLIPSIS + ('betainc', ...) Note that: - * Both :func:`scico.numpy.ravel` and :meth:`.BlockArray.ravel` return a - :class:`.BlockArray` with ravelled blocks rather than the concatenation - of these blocks as a single array. - * The functional and method versions of the "same" function differ in their - behavior, with the method version only applying the reduction within each - block, and the function version applying the reduction across all blocks. - For example, :func:`scico.numpy.sum` applied to a :class:`.BlockArray` with - two blocks returns a scalar value, while :meth:`.BlockArray.sum` returns a - :class:`.BlockArray` two scalar blocks. + +* Both :func:`scico.numpy.ravel` and :meth:`.BlockArray.ravel` return a + :class:`.BlockArray` with ravelled blocks rather than the concatenation + of these blocks as a single array. +* The functional and method versions of the "same" function differ in their + behavior, with the method version only applying the reduction within each + block, and the function version applying the reduction across all blocks. + For example, :func:`scico.numpy.sum` applied to a :class:`.BlockArray` with + two blocks returns a scalar value, while :meth:`.BlockArray.sum` returns a + :class:`.BlockArray` two scalar blocks. Motivating Example @@ -124,25 +125,25 @@ Instead, we can construct a :class:`.BlockArray`, :math:`\mb{x}_B = [\mb{x}_h, \mb{x}_v]`: - :: +:: - >>> n = 32 - >>> m = 16 - >>> x_h, key = scico.random.randn((n, m-1)) - >>> x_v, _ = scico.random.randn((n-1, m), key=key) + >>> n = 32 + >>> m = 16 + >>> x_h, key = scico.random.randn((n, m-1)) + >>> x_v, _ = scico.random.randn((n-1, m), key=key) - # Form the blockarray - >>> x_B = snp.blockarray([x_h, x_v]) + # Form the blockarray + >>> x_B = snp.blockarray([x_h, x_v]) - # The blockarray shape is a tuple of tuples - >>> x_B.shape - ((32, 15), (31, 16)) + # The blockarray shape is a tuple of tuples + >>> x_B.shape + ((32, 15), (31, 16)) - # Each block component can be easily accessed - >>> x_B[0].shape - (32, 15) - >>> x_B[1].shape - (31, 16) + # Each block component can be easily accessed + >>> x_B[0].shape + (32, 15) + >>> x_B[1].shape + (31, 16) Constructing a BlockArray @@ -151,18 +152,18 @@ Constructing a BlockArray The recommended way to construct a :class:`.BlockArray` is by using the :func:`snp.blockarray` function. - :: +:: - >>> import scico.numpy as snp - >>> x0, key = scico.random.randn((32, 32)) - >>> x1, _ = scico.random.randn((16,), key=key) - >>> X = snp.blockarray((x0, x1)) - >>> X.shape - ((32, 32), (16,)) - >>> X.size - (1024, 16) - >>> len(X) - 2 + >>> import scico.numpy as snp + >>> x0, key = scico.random.randn((32, 32)) + >>> x1, _ = scico.random.randn((16,), key=key) + >>> X = snp.blockarray((x0, x1)) + >>> X.shape + ((32, 32), (16,)) + >>> X.size + (1024, 16) + >>> len(X) + 2 While :func:`.snp.blockarray` will accept either :class:`~numpy.ndarray`\ s or :class:`~jax.Array`\ s as input, :class:`~numpy.ndarray`\ s will be converted to @@ -189,18 +190,18 @@ to work on instances of :class:`.BlockArray` in addition to instances of :obj:`~jax.Array`. For example - :: +:: - >>> x, key = scico.random.randn((3, 4)) - >>> A_1 = scico.linop.Identity(x.shape) - >>> A_1.shape # array -> array - ((3, 4), (3, 4)) + >>> x, key = scico.random.randn((3, 4)) + >>> A_1 = scico.linop.Identity(x.shape) + >>> A_1.shape # array -> array + ((3, 4), (3, 4)) - >>> A_2 = scico.linop.FiniteDifference(x.shape) - >>> A_2.shape # array -> BlockArray - (((2, 4), (3, 3)), (3, 4)) + >>> A_2 = scico.linop.FiniteDifference(x.shape) + >>> A_2.shape # array -> BlockArray + (((2, 4), (3, 3)), (3, 4)) - >>> diag = snp.blockarray([np.array(1.0), np.array(2.0)]) - >>> A_3 = scico.linop.Diagonal(diag, input_shape=(A_2.output_shape)) - >>> A_3.shape # BlockArray -> BlockArray - (((2, 4), (3, 3)), ((2, 4), (3, 3))) + >>> diag = snp.blockarray([np.array(1.0), np.array(2.0)]) + >>> A_3 = scico.linop.Diagonal(diag, input_shape=(A_2.output_shape)) + >>> A_3.shape # BlockArray -> BlockArray + (((2, 4), (3, 3)), ((2, 4), (3, 3))) diff --git a/docs/source/include/examplenotes.rst b/docs/source/include/examplenotes.rst index ba10dfe07..0781478f0 100644 --- a/docs/source/include/examplenotes.rst +++ b/docs/source/include/examplenotes.rst @@ -7,10 +7,10 @@ Some examples use additional dependencies, which are listed in `examples_require The additional requirements should be installed via pip, with the exception of ``astra-toolbox``, which should be installed via conda: - :: +:: - conda install -c astra-toolbox astra-toolbox - pip install -r examples/examples_requirements.txt # Installs other example requirements + conda install -c astra-toolbox astra-toolbox + pip install -r examples/examples_requirements.txt # Installs other example requirements The dependencies can also be installed individually as required. diff --git a/docs/source/include/functional.rst b/docs/source/include/functional.rst index b002e60eb..feb396e47 100644 --- a/docs/source/include/functional.rst +++ b/docs/source/include/functional.rst @@ -1,27 +1,6 @@ Functionals =========== -.. raw:: html - - - A functional is a mapping from :math:`\mathbb{R}^n` or :math:`\mathbb{C}^n` to :math:`\mathbb{R}`. In SCICO, functionals are @@ -77,13 +56,13 @@ Scaled Functionals Given a scalar ``c`` and a functional ``f`` with a defined proximal method, we can determine the proximal method of ``c * f`` as - .. math:: +.. math:: - \begin{align} - \mathrm{prox}_{c f} (v, \lambda) &= \argmin_x \lambda (c f)(x) + \frac{1}{2} \norm{v - x}_2^2 \\ - &= \argmin_x (\lambda c) f(x) + \frac{1}{2} \norm{v - x}_2^2 \\ - &= \mathrm{prox}_{f} (v, c \lambda) - \end{align} + \begin{align} + \mathrm{prox}_{c f} (v, \lambda) &= \argmin_x \lambda (c f)(x) + \frac{1}{2} \norm{v - x}_2^2 \\ + &= \argmin_x (\lambda c) f(x) + \frac{1}{2} \norm{v - x}_2^2 \\ + &= \mathrm{prox}_{f} (v, c \lambda) + \end{align} Note that we have made no assumptions regarding homogeneity of ``f``; rather, only that the proximal method of ``f`` is given @@ -102,21 +81,21 @@ Separable Functionals A separable functional :math:`f : \mathbb{C}^N \to \mathbb{R}` can be written as the sum of functionals :math:`f_i : \mathbb{C}^{N_i} \to \mathbb{R}` with :math:`\sum_i N_i = N`. In particular, - .. math:: - f(\mb{x}) = f(\mb{x}_1, \dots, \mb{x}_N) = f_1(\mb{x}_1) + \dots + f_N(\mb{x}_N) +.. math:: + f(\mb{x}) = f(\mb{x}_1, \dots, \mb{x}_N) = f_1(\mb{x}_1) + \dots + f_N(\mb{x}_N) The proximal operator of a separable :math:`f` can be written in terms of the proximal operators of the :math:`f_i` (see Theorem 6.6 of :cite:`beck-2017-first`): - .. math:: - \mathrm{prox}_f(\mb{x}, \lambda) - = - \begin{bmatrix} - \mathrm{prox}_{f_1}(\mb{x}_1, \lambda) \\ - \vdots \\ - \mathrm{prox}_{f_N}(\mb{x}_N, \lambda) \\ - \end{bmatrix} +.. math:: + \mathrm{prox}_f(\mb{x}, \lambda) + = + \begin{bmatrix} + \mathrm{prox}_{f_1}(\mb{x}_1, \lambda) \\ + \vdots \\ + \mathrm{prox}_{f_N}(\mb{x}_N, \lambda) \\ + \end{bmatrix} Separable Functionals are implemented in the :class:`.SeparableFunctional` class. Separable functionals naturally accept :class:`.BlockArray` inputs and return the prox as a :class:`.BlockArray`. @@ -134,18 +113,18 @@ create a class which For example, - :: +:: - class MyFunctional(scico.functional.Functional): + class MyFunctional(scico.functional.Functional): - has_eval = True - has_prox = True + has_eval = True + has_prox = True - def _eval(self, x: JaxArray) -> float: - return snp.sum(x) + def _eval(self, x: JaxArray) -> float: + return snp.sum(x) - def prox(self, x: JaxArray, lam : float) -> JaxArray: - return x - lam + def prox(self, x: JaxArray, lam : float) -> JaxArray: + return x - lam Losses @@ -153,8 +132,8 @@ Losses In SCICO, a loss is a special type of functional - .. math:: - f(\mb{x}) = \alpha l( \mb{y}, A(\mb{x}) ) +.. math:: + f(\mb{x}) = \alpha l( \mb{y}, A(\mb{x}) ) where :math:`\alpha` is a scaling parameter, :math:`l` is a functional, diff --git a/docs/source/include/learning.rst b/docs/source/include/learning.rst index 7e9ab149d..392fb841b 100644 --- a/docs/source/include/learning.rst +++ b/docs/source/include/learning.rst @@ -26,8 +26,8 @@ The unrolled optimization with deep priors (ODP) :cite:`diamond-2018-odp`, imple The framework aims to solve the optimization problem - .. math:: - \argmin_{\mb{x}} \; f(A \mb{x}, \mb{y}) + r(\mb{x}) \;, +.. math:: + \argmin_{\mb{x}} \; f(A \mb{x}, \mb{y}) + r(\mb{x}) \;, where :math:`A` represents a linear forward model and :math:`r` a regularization function encoding prior information, by unrolling the iterative solution method into a network where each iteration corresponds to a different stage in the ODP network. Different iterative solutions produce different unrolled optimization algorithms which, in turn, produce different ODP networks. The ones implemented in SCICO are described below. @@ -37,25 +37,25 @@ Proximal Map This algorithm corresponds to solving - .. math:: - :label: eq:odp_prox +.. math:: + :label: eq:odp_prox - \argmin_{\mb{x}} \; \alpha_k \, f(A \mb{x}, \mb{y}) + \frac{1}{2} \| \mb{x} - \mb{x}^k - \mb{x}^{k+1/2} \|_2^2 \;, + \argmin_{\mb{x}} \; \alpha_k \, f(A \mb{x}, \mb{y}) + \frac{1}{2} \| \mb{x} - \mb{x}^k - \mb{x}^{k+1/2} \|_2^2 \;, with :math:`k` corresponding to the index of the iteration, which translates to an index of the stage of the network, :math:`f(A \mb{x}, \mb{y})` a fidelity term, usually an :math:`\ell_2` norm, and :math:`\mb{x}^{k+1/2}` a regularization representing :math:`\mathrm{prox}_r (\mb{x}^k)` and usually implemented as a convolutional neural network (CNN). This proximal map representation is used when minimization problem :eq:`eq:odp_prox` can be solved in a computationally efficient manner. :class:`.ODPProxDnBlock` uses this formulation to solve a denoising problem, which, according to :cite:`diamond-2018-odp`, can be solved by - .. math:: - \mb{x}^{k+1} = (\alpha_k \, \mb{y} + \mb{x}^k + \mb{x}^{k+1/2}) \, / \, (\alpha_k + 1) \;, +.. math:: + \mb{x}^{k+1} = (\alpha_k \, \mb{y} + \mb{x}^k + \mb{x}^{k+1/2}) \, / \, (\alpha_k + 1) \;, where :math:`A` corresponds to the identity operator and is therefore omitted, :math:`\mb{y}` is the noisy signal, :math:`\alpha_k > 0` is a learned stage-wise parameter weighting the contribution of the fidelity term and :math:`\mb{x}^k + \mb{x}^{k+1/2}` is the regularization, usually represented by a residual CNN. :class:`.ODPProxDblrBlock` uses this formulation to solve a deblurring problem, which, according to :cite:`diamond-2018-odp`, can be solved by - .. math:: - \mb{x}^{k+1} = \mathcal{F}^{-1} \mathrm{diag} (\alpha_k | \mathcal{F}(K)|^2 + 1 )^{-1} \mathcal{F} \, (\alpha_k K^T * \mb{y} + \mb{x}^k + \mb{x}^{k+1/2}) \;, +.. math:: + \mb{x}^{k+1} = \mathcal{F}^{-1} \mathrm{diag} (\alpha_k | \mathcal{F}(K)|^2 + 1 )^{-1} \mathcal{F} \, (\alpha_k K^T * \mb{y} + \mb{x}^k + \mb{x}^{k+1/2}) \;, where :math:`A` is the blurring operator, :math:`K` is the blurring kernel, :math:`\mb{y}` is the blurred signal, :math:`\mathcal{F}` is the DFT, :math:`\alpha_k > 0` is a learned stage-wise parameter weighting the contribution of the fidelity term and :math:`\mb{x}^k + \mb{x}^{k+1/2}` is the regularization represented by a residual CNN. @@ -65,15 +65,15 @@ Gradient Descent When the solution of the optimization problem in :eq:`eq:odp_prox` can not be simply represented by an analytical step, a formulation based on a gradient descent iteration is preferred. This yields - .. math:: - \mb{x}^{k+1} = \mb{x}^k + \mb{x}^{k+1/2} - \alpha_k \, A^T \nabla_x \, f(A \mb{x}^k, \mb{y}) \;, +.. math:: + \mb{x}^{k+1} = \mb{x}^k + \mb{x}^{k+1/2} - \alpha_k \, A^T \nabla_x \, f(A \mb{x}^k, \mb{y}) \;, where :math:`\mb{x}^{k+1/2}` represents :math:`\nabla r(\mb{x}^k)`. :class:`.ODPGrDescBlock` uses this formulation to solve a generic problem with :math:`\ell_2` fidelity as - .. math:: - \mb{x}^{k+1} = \mb{x}^k + \mb{x}^{k+1/2} - \alpha_k \, A^T (A \mb{x} - \mb{y}) \;, +.. math:: + \mb{x}^{k+1} = \mb{x}^k + \mb{x}^{k+1/2} - \alpha_k \, A^T (A \mb{x} - \mb{y}) \;, with :math:`\mb{y}` the measured signal and :math:`\mb{x} + \mb{x}^{k+1/2}` a residual CNN. @@ -83,12 +83,12 @@ MoDL The model-based deep learning (MoDL) :cite:`aggarwal-2019-modl`, implemented as :class:`.MoDLNet`, is used to solve inverse problems in imaging also by adapting classical iterative methods into an end-to-end deep learning framework, but, in contrast to ODP, it solves the optimization problem - .. math:: - \argmin_{\mb{x}} \; \| A \mb{x} - \mb{y}\|_2^2 + \lambda \, \| \mb{x} - \mathrm{D}_w(\mb{x})\|_2^2 \;, +.. math:: + \argmin_{\mb{x}} \; \| A \mb{x} - \mb{y}\|_2^2 + \lambda \, \| \mb{x} - \mathrm{D}_w(\mb{x})\|_2^2 \;, by directly computing the update - .. math:: - \mb{x}^{k+1} = (A^T A + \lambda \, I)^{-1} (A^T \mb{y} + \lambda \, \mb{z}^k) \;, +.. math:: + \mb{x}^{k+1} = (A^T A + \lambda \, I)^{-1} (A^T \mb{y} + \lambda \, \mb{z}^k) \;, via conjugate gradient. The regularization :math:`\mb{z}^k = \mathrm{D}_w(\mb{x}^{k})` incorporates prior information, usually in the form of a denoiser model. In this case, the denoiser :math:`\mathrm{D}_w` is shared between all the stages of the network requiring relatively less memory than other unrolling methods. This also allows for deploying a different number of iterations in testing than the ones used in training. diff --git a/docs/source/include/operator.rst b/docs/source/include/operator.rst index ab21ba1b9..26a35ad82 100644 --- a/docs/source/include/operator.rst +++ b/docs/source/include/operator.rst @@ -23,14 +23,14 @@ In SCICO, this linear operator will return a :class:`.BlockArray` with the horiz stored as blocks. Letting :math:`y = \mb{A} x`, we have ``y.shape = ((n, m-1), (n-1, m))`` and - :: +:: - A.input_shape = (n, m) - A.output_shape = ((n, m-1), (n-1, m)], (n, m)) - A.shape = ( ((n, m-1), (n-1, m)), (n, m)) # (output_shape, input_shape) - A.input_size = n*m - A.output_size = n*(n-1)*m*(m-1) - A.matrix_shape = (n*(n-1)*m*(m-1), n*m) # (output_size, input_size) + A.input_shape = (n, m) + A.output_shape = ((n, m-1), (n-1, m)], (n, m)) + A.shape = ( ((n, m-1), (n-1, m)), (n, m)) # (output_shape, input_shape) + A.input_size = n*m + A.output_size = n*(n-1)*m*(m-1) + A.matrix_shape = (n*(n-1)*m*(m-1), n*m) # (output_size, input_size) Operator Calculus @@ -65,23 +65,23 @@ Defining A New Operator To define a new operator, pass a callable to the :class:`.Operator` constructor: - :: +:: - A = Operator(input_shape=(32,), - eval_fn = lambda x: 2 * x) + A = Operator(input_shape=(32,), + eval_fn = lambda x: 2 * x) Or use subclassing: - :: +:: - >>> from scico.operator import Operator - >>> class MyOp(Operator): - ... - ... def _eval(self, x): - ... return 2 * x + >>> from scico.operator import Operator + >>> class MyOp(Operator): + ... + ... def _eval(self, x): + ... return 2 * x - >>> A = MyOp(input_shape=(32,)) + >>> A = MyOp(input_shape=(32,)) At a minimum, the ``_eval`` function must be overridden. If either ``output_shape`` or ``output_dtype`` are unspecified, they are determined by evaluating @@ -93,9 +93,9 @@ Linear Operators Linear operators are those for which - .. math:: +.. math:: - H(a \mb{x} + b \mb{y}) = a H(\mb{x}) + b H(\mb{y}). + H(a \mb{x} + b \mb{y}) = a H(\mb{x}) + b H(\mb{y}). SCICO represents linear operators as instances of the class :class:`.LinearOperator`. While finite-dimensional linear operators @@ -194,22 +194,21 @@ Defining A New Linear Operator To define a new linear operator, pass a callable to the :class:`.LinearOperator` constructor - :: - - >>> from scico.linop import LinearOperator - >>> A = LinearOperator(input_shape=(32,), - ... eval_fn = lambda x: 2 * x) +:: + >>> from scico.linop import LinearOperator + >>> A = LinearOperator(input_shape=(32,), + ... eval_fn = lambda x: 2 * x) Or, use subclassing: - :: +:: - >>> class MyLinearOperator(LinearOperator): - ... def _eval(self, x): - ... return 2 * x + >>> class MyLinearOperator(LinearOperator): + ... def _eval(self, x): + ... return 2 * x - >>> A = MyLinearOperator(input_shape=(32,)) + >>> A = MyLinearOperator(input_shape=(32,)) At a minimum, the ``_eval`` method must be overridden. If the ``_adj`` method is not overriden, the adjoint is determined using :func:`scico.linear_adjoint`. diff --git a/docs/source/include/optimizer.rst b/docs/source/include/optimizer.rst index 7c24a826f..16a807b21 100644 --- a/docs/source/include/optimizer.rst +++ b/docs/source/include/optimizer.rst @@ -3,31 +3,6 @@ Optimization Algorithms ======================= - -.. raw:: html - - - - - ADMM ---- @@ -84,11 +59,11 @@ Subproblem Solvers The most computational expensive component of the ADMM iterations is typically the :math:`\mb{x}`-update, - .. math:: - :label: eq:admm_x_step +.. math:: + :label: eq:admm_x_step - \argmin_{\mb{x}} \; f(\mb{x}) + \sum_i \frac{\rho_i}{2} - \norm{\mb{z}^{(k)}_i - \mb{u}^{(k)}_i - C_i \mb{x}}_2^2 \;. + \argmin_{\mb{x}} \; f(\mb{x}) + \sum_i \frac{\rho_i}{2} + \norm{\mb{z}^{(k)}_i - \mb{u}^{(k)}_i - C_i \mb{x}}_2^2 \;. The available solvers for this problem are: @@ -126,17 +101,17 @@ Linearized ADMM algorithm :cite:`yang-2012-linearized` :cite:`parikh-2014-proximal` (Sec. 4.4.2) is an algorithm for solving problems of the form - .. math:: - \argmin_{\mb{x}} \; f(\mb{x}) + g(C \mb{x}) \;, +.. math:: + \argmin_{\mb{x}} \; f(\mb{x}) + g(C \mb{x}) \;, where :math:`f` and :math:`g` are are convex (but not necessarily smooth) functions. Although convergence per iteration is typically significantly worse than that of ADMM, the :math:`\mb{x}`-update, - .. math:: +.. math:: - \mathrm{prox}_{\mu f} \left( \mb{x}^{(k)} - (\mu / \nu) C^T - \left(C \mb{x}^{(k)} - \mb{z}^{(k)} + \mb{u}^{(k)} \right) \right) + \mathrm{prox}_{\mu f} \left( \mb{x}^{(k)} - (\mu / \nu) C^T + \left(C \mb{x}^{(k)} - \mb{z}^{(k)} + \mb{u}^{(k)} \right) \right) is can be much cheaper than that of ADMM, giving Linearized ADMM competitive time convergence performance. @@ -155,8 +130,8 @@ The Primal–Dual Hybrid Gradient (PDHG) algorithm :cite:`esser-2010-general` :cite:`chambolle-2010-firstorder` :cite:`pock-2011-diagonal` solves problems of the form - .. math:: - \argmin_{\mb{x}} \; f(\mb{x}) + g(C \mb{x}) \;, +.. math:: + \argmin_{\mb{x}} \; f(\mb{x}) + g(C \mb{x}) \;, where :math:`f` and :math:`g` are are convex (but not necessarily smooth) functions. The algorithm has similar advantages over ADMM to those of Linearized ADMM, but typically exhibits better convergence properties. diff --git a/docs/source/index.rst b/docs/source/index.rst index 8b1099fc9..375c254fe 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -9,10 +9,10 @@ SCICO Documentation inverse advantages install - examples classes - API Reference <_autosummary/scico.rst> notes + examples + API Reference <_autosummary/scico.rst> zreferences .. toctree:: @@ -24,13 +24,6 @@ SCICO Documentation style -.. toctree:: - :hidden: - - api - examples/index.ipynb - - Indices ======= diff --git a/docs/source/install.rst b/docs/source/install.rst index ebcc1dbdc..8eec843e5 100644 --- a/docs/source/install.rst +++ b/docs/source/install.rst @@ -22,9 +22,9 @@ From PyPI The simplest way to install the most recent release of SCICO from `PyPI `_ is - :: +:: - pip install scico + pip install scico From GitHub diff --git a/docs/source/inverse.rst b/docs/source/inverse.rst index d177b98a0..4464c73b3 100644 --- a/docs/source/inverse.rst +++ b/docs/source/inverse.rst @@ -1,29 +1,6 @@ Inverse Problems ================ - -.. raw:: html - - - - In traditional imaging, the burden of image formation is placed on physical components, such as a lens, with the resulting image being taken from the sensor with minimal processing. In computational diff --git a/docs/source/overview.rst b/docs/source/overview.rst index 5edcaafbd..ad44e4920 100644 --- a/docs/source/overview.rst +++ b/docs/source/overview.rst @@ -1,30 +1,6 @@ Overview ======== - -.. raw:: html - - - - - `Scientific Computational Imaging Code (SCICO) `__ is a Python package for solving the inverse problems that arise in scientific imaging applications. Its diff --git a/docs/source/style.rst b/docs/source/style.rst index d375fa5fe..f26bcb85a 100644 --- a/docs/source/style.rst +++ b/docs/source/style.rst @@ -4,27 +4,6 @@ Style Guide =========== -.. raw:: html - - - Overview -------- @@ -59,7 +38,7 @@ interface for functions or methods. Naming ------ -We follow the `Google naming conventions `_ listed here: +We follow the `Google naming conventions `_: .. list-table:: Naming Conventions :widths: 20 20 @@ -105,26 +84,24 @@ Example: .. code:: Python - fldln = 5 # field length + fldln = 5 # field length This could be improved by using the descriptive variable ``field_len``. Things to avoid: - Single character names except for the following special cases: - - - counters or iterators (``i``, ``j``); - - `e` as an exception identifier (``Exception e``); - - `f` as a file in ``with`` statements; - - mathematical notation in which a reference to the paper or - algorithm with said notation is preferred if not clear from the - intended purpose. + - counters or iterators (``i``, ``j``); + - `e` as an exception identifier (``Exception e``); + - `f` as a file in ``with`` statements; + - mathematical notation in which a reference to the paper or + algorithm with said notation is preferred if not clear from the + intended purpose. - Trailing underscores unless the component is meant to be protected or private: - - - protected: Use a single underscore, ``_``, for protected access; and - - pseudo-private: Use double underscores, ``__``, for - pseudo-private access via name mangling. + - protected: Use a single underscore, ``_``, for protected access; and + - pseudo-private: Use double underscores, ``__``, for + pseudo-private access via name mangling. Displaying and Printing Strings @@ -137,9 +114,9 @@ syntax. For example: .. code:: Python - state = "active" - print("The state is %s" % state) # Not preferred - print(f"The state is {state}") # Preferred + state = "active" + print("The state is %s" % state) # Not preferred + print(f"The state is {state}") # Preferred Imports @@ -151,13 +128,13 @@ use of ``import`` statements should be reserved for packages and modules only, i.e. individual classes and functions should not be imported. The only exception to this is the typing module. -- Use ``import x`` for importing packages and modules, where x is the - package or module name. +- Use ``import x`` for importing packages and modules, where x is the package or + module name. - Use ``from x import y`` where x is the package name and y is the module name. - Use ``from x import y as z`` if two modules named ``y`` are imported - or if ``y`` is too long of a name. + or if ``y`` is too long of a name. - Use ``import y as z`` when ``z`` is a standard abbreviation like - ``import numpy as np``. + ``import numpy as np``. Variables @@ -171,9 +148,9 @@ practices that can be applied to variables such as: - One may type a variables by using a ``: type`` before the function value is assigned, e.g., - .. code-block:: python + .. code-block:: python - a: Foo = SomeDecoratedFunction() + a: Foo = SomeDecoratedFunction() - Avoid global variables. - A function can refer to variables defined in enclosing functions but @@ -420,8 +397,8 @@ classes, or method definitions. - Notes - - Provides additional information about the code. May include - mathematical equations in LaTeX format. For example, + - Provide additional information about the code. May include + mathematical equations in LaTeX format. For example, .. code-block:: python @@ -433,7 +410,7 @@ classes, or method definitions. X(e^{j\omega } ) = x(n)e^{ - j\omega n} """ - | Additionally, math can be used inline: + Math can also be used inline: .. code-block:: python @@ -443,8 +420,8 @@ classes, or method definitions. The value of :math:`\omega` is larger than 5. """ - | For a list of available LaTex macros, search for "macros" in - `docs/source/conf.py `_. + For a list of available LaTex macros, search for "macros" in + `docs/source/conf.py `_. - Examples: @@ -545,21 +522,21 @@ For additional grammar and usage guidance, refer to `The Chicago Manual of Style `_. A few notable guidelines: - * Equations which conclude a sentence should end with a period, - e.g., "Poisson's equation is +* Equations which conclude a sentence should end with a period, + e.g., "Poisson's equation is - .. math:: + .. math:: - \Delta \varphi = f \;." + \Delta \varphi = f \;." - * Do not capitalize acronyms or inititalisms when defining them, - e.g., "computer-aided system engineering (CASE)," - "fast Fourier transform (FFT)." +* Do not capitalize acronyms or inititalisms when defining them, + e.g., "computer-aided system engineering (CASE)," + "fast Fourier transform (FFT)." - * Avoid capitalization in text except where absolutely necessary, - e.g., "Newton’s first law." +* Avoid capitalization in text except where absolutely necessary, + e.g., "Newton’s first law." - * Use a single space after the period at the end of a sentence. +* Use a single space after the period at the end of a sentence. The source code (`.rst` files) for these pages does not have a hard diff --git a/docs/source/team.rst b/docs/source/team.rst index bf2cbda1a..79945505c 100644 --- a/docs/source/team.rst +++ b/docs/source/team.rst @@ -1,29 +1,6 @@ Developers ========== - -.. raw:: html - - - - Core Developers --------------- diff --git a/examples/makeindex.py b/examples/makeindex.py index f1e63cb62..ac42a1589 100755 --- a/examples/makeindex.py +++ b/examples/makeindex.py @@ -9,6 +9,7 @@ import re from pathlib import Path +import nbformat as nbf import py2jn import pypandoc @@ -67,6 +68,9 @@ # Convert from python to notebook format and write notebook nb = py2jn.py_string_to_notebook(md_text) py2jn.tools.write_notebook(nb, dst, nbver=4) +nb = nbf.read(dst, nbf.NO_CONVERT) +nb.metadata = {"nbsphinx": {"orphan": True}} +nbf.write(nb, dst) # Build examples index for docs diff --git a/scico/flax/examples/data_generation.py b/scico/flax/examples/data_generation.py index 0b2ccc8e4..07241c7cd 100644 --- a/scico/flax/examples/data_generation.py +++ b/scico/flax/examples/data_generation.py @@ -7,7 +7,7 @@ """Functionality to generate training data for Flax example scripts. -It distributes computations via ray (if available) or jax or to reduce +Computation is distributed via ray (if available) or jax or to reduce processing time. """ @@ -60,9 +60,8 @@ class Foam2(UnitCircle): """Foam-like material with two attenuations. - Define functionality to generate phantom with structure - similar to foam - with two different attenuation properties.""" + Define functionality to generate phantom with structure similar + to foam with two different attenuation properties.""" def __init__( self, @@ -130,8 +129,8 @@ def generate_foam2_images(seed: float, size: int, ndata: int) -> Array: def generate_foam1_images(seed: float, size: int, ndata: int) -> Array: """Generate batch of xdesign foam-like structures. - Generate batch of images with `xdesign` foam-like structure, - which uses one attenuation. + Generate batch of images with `xdesign` foam-like structure, which + uses one attenuation. Args: seed: Seed for data generation. @@ -166,7 +165,7 @@ def generate_ct_data( """Generate batch of computed tomography (CT) data. Generate batch of CT data for training of machine learning network - models. + models. Args: nimg: Number of images to generate. @@ -256,7 +255,7 @@ def generate_blur_data( """Generate batch of blurred data. Generate batch of blurred data for training of machine learning - network models. + network models. Args: nimg: Number of images to generate. @@ -336,7 +335,7 @@ def distributed_data_generation( Default: ``True``. Returns: - nd-array of generated data. + Array of generated data. """ nproc = jax.device_count() seeds = jnp.arange(nproc) @@ -368,7 +367,7 @@ def ray_distributed_data_generation( ``False``. Returns: - nd-array of generated data. + Array of generated data. """ if not have_ray: raise RuntimeError("Package ray is required for use of this function.") diff --git a/scico/numpy/_blockarray.py b/scico/numpy/_blockarray.py index 9c8545ae9..72694608a 100644 --- a/scico/numpy/_blockarray.py +++ b/scico/numpy/_blockarray.py @@ -5,7 +5,7 @@ # and user license can be found in the 'LICENSE.txt' file distributed # with the package. -r"""Block array class.""" +"""Block array class.""" import inspect from functools import wraps @@ -22,13 +22,16 @@ class BlockArray: - """Block array class, which provides a way to combine arrays of - different shapes into a single object for use with other SCICO classes. + """Block array class. - For detailed documentation, see the :ref:`detailed BlockArray documentation `. + A block array provides a way to combine arrays of different shapes + into a single object for use with other SCICO classes. For further + information, see the + :ref:`detailed BlockArray documentation `. Example ------- + >>> x = snp.blockarray(( ... [[1, 3, 7], ... [2, 2, 1]], @@ -38,7 +41,6 @@ class BlockArray: ((2, 3), (3,)) >>> snp.sum(x) Array(30, dtype=int32) - """ # Ensure we use BlockArray.__radd__, __rmul__, etc for binary diff --git a/scico/numpy/_wrappers.py b/scico/numpy/_wrappers.py index fe087de88..d91b1db4b 100644 --- a/scico/numpy/_wrappers.py +++ b/scico/numpy/_wrappers.py @@ -69,7 +69,7 @@ def wrap_recursively( def map_func_over_tuple_of_tuples(func: Callable, map_arg_name: Optional[str] = "shape"): """Wrap a function so that it automatically maps over a tuple of tuples - argument, returning a `BlockArray`. + argument, returning a BlockArray. """ @wraps(func) @@ -95,7 +95,7 @@ def mapped(*args, **kwargs): def map_func_over_blocks(func): - """Wrap a function so that it maps over all of its `BlockArray` + """Wrap a function so that it maps over all of its BlockArray arguments. """ @@ -127,12 +127,14 @@ def mapped(*args, **kwargs): def add_full_reduction(func: Callable, axis_arg_name: Optional[str] = "axis"): - """Wrap a function so that it can fully reduce a `BlockArray`. If + """Wrap a function so that it can fully reduce a BlockArray. + + Wrap a function so that it can fully reduce a :class:`.BlockArray`. If nothing is passed for the axis argument and the function is called - on a `BlockArray`, it is fully ravelled before the function is + on a :class:`.BlockArray`, it is fully ravelled before the function is called. - Should be outside `map_func_over_blocks`. + Should be outside :func:`map_func_over_blocks`. """ sig = signature(func) if axis_arg_name not in sig.parameters: diff --git a/scico/numpy/util.py b/scico/numpy/util.py index 2dce7273e..cfbf8b8b7 100644 --- a/scico/numpy/util.py +++ b/scico/numpy/util.py @@ -215,7 +215,6 @@ def is_nested(x: Any) -> bool: ``True`` if `x` is a list/tuple containing at least one list/tuple, ``False`` otherwise. - Example: >>> is_nested([1, 2, 3]) False