diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst index 3399aa774f..1a5c5945da 100644 --- a/docs/_templates/autosummary/class.rst +++ b/docs/_templates/autosummary/class.rst @@ -7,7 +7,11 @@ .. autosummary:: {% for item in attributes %} {% if not item.startswith('_') %} + {% if '.' in item %} + {{ name }}.{{ item }} + {% else %} ~{{ name }}.{{ item }} + {% endif %} {% endif %} {%- endfor %} {% endif %} @@ -26,7 +30,11 @@ 'step_either', 'step_to', 'isDataset', 'isDimension', 'isGroup', 'isRoot', 'isVariable']) %} - ~{{ name }}.{{ item }} + {% if '.' in item %} + {{ name }}.{{ item }} + {% else %} + ~{{ name }}.{{ item }} + {% endif %} {% endif %} {%- endfor %} {% endif %} diff --git a/docs/api/viz/index.rst b/docs/api/viz/index.rst index 55e5a9a739..cf396406f1 100644 --- a/docs/api/viz/index.rst +++ b/docs/api/viz/index.rst @@ -26,6 +26,7 @@ Plot classes are workflow classes that implement some specific plotting. GridPlot WavefunctionPlot PdosPlot + AtomicMatrixPlot Utilities --------- diff --git a/docs/conf.py b/docs/conf.py index 5084db907d..adbb430450 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -471,6 +471,8 @@ class RevYearPlain(PlainStyle): nbsphinx_thumbnails = {} +nbsphinx_allow_errors = True + import inspect @@ -573,6 +575,100 @@ def sisl_skip(app, what, name, obj, skip, options): return skip +from functools import wraps + +import sisl.viz +from sisl.viz._plotables import ALL_PLOT_HANDLERS + + +def document_nested_attribute(obj, owner_cls, attribute_path: str): + """Sets a nested attribute to a class with a placeholder name. + + It substitutes dots in the attribute name with a placeholder. This substitution + will be reversed once the documentation is built. + + This is needed because autodoc refuses to document attributes with dots in their name + (although python allows for that possibility). + """ + + setattr(owner_cls, attribute_path, obj) + setattr( + getattr(owner_cls, attribute_path.split(".")[0]), + ".".join(attribute_path.split(".")[1:]), + obj, + ) + + +def document_nested_method( + method, owner_cls, method_path: str, add_signature_self: bool = False +): + """Takes a nested method, wraps it to make sure is of function type and creates a nested attribute in the owner class.""" + + @wraps(method) + def method_wrapper(*args, **kwargs): + return method(*args, **kwargs) + + if add_signature_self: + wrapper_sig = inspect.signature(method_wrapper) + method_wrapper.__signature__ = wrapper_sig.replace( + parameters=[ + inspect.Parameter("self", inspect.Parameter.POSITIONAL_ONLY), + *wrapper_sig.parameters.values(), + ] + ) + + document_nested_attribute(method_wrapper, owner_cls, method_path) + + setattr( + getattr(owner_cls, method_path.split(".")[0]), + method_path.split(".")[1], + method_wrapper, + ) + + +def document_class_dispatcher_methods( + dispatcher, + owner_cls, + dispatcher_path: str, + add_signature_self: bool = False, + as_attributes: bool = False, +): + """Document all methods in a dispatcher class as nested methods in the owner class.""" + for key, method in dispatcher._dispatchs.items(): + if not isinstance(key, str): + continue + if as_attributes: + document_nested_attribute(method, owner_cls, f"{dispatcher_path}.{key}") + else: + document_nested_method( + method, + owner_cls, + f"{dispatcher_path}.{key}", + add_signature_self=add_signature_self, + ) + + +# Document all plotting possibilities of each plot handler +for plot_handler in ALL_PLOT_HANDLERS: + document_class_dispatcher_methods( + plot_handler, plot_handler._cls, "plot", add_signature_self=True + ) + +# Document the methods of the Geometry.to dispatcher +document_class_dispatcher_methods( + sisl.Geometry.to, sisl.Geometry, "to", add_signature_self=False +) + +# Document the dispatchers within the BrillouinZone.apply dispatcher +document_class_dispatcher_methods( + sisl.BrillouinZone.apply, + sisl.BrillouinZone, + "apply", + add_signature_self=False, + as_attributes=True, +) + + def setup(app): # Setup autodoc skipping app.connect("autodoc-skip-member", sisl_skip) diff --git a/src/sisl/viz/_plotables.py b/src/sisl/viz/_plotables.py index 93ea66027b..f3246a0e36 100644 --- a/src/sisl/viz/_plotables.py +++ b/src/sisl/viz/_plotables.py @@ -14,6 +14,8 @@ __all__ = ["register_plotable", "register_data_source", "register_sile_method"] +ALL_PLOT_HANDLERS = [] + class ClassPlotHandler(ClassDispatcher): """Handles all plotting possibilities for a class""" @@ -25,6 +27,10 @@ def __init__(self, cls, *args, inherited_handlers=(), **kwargs): kwargs["type_dispatcher"] = None super().__init__(*args, inherited_handlers=inherited_handlers, **kwargs) + ALL_PLOT_HANDLERS.append(self) + + self.__doc__ = f"Plotting functions for the `{cls.__name__}` class." + self._dispatchs = ChainMap( self._dispatchs, *[handler._dispatchs for handler in inherited_handlers] ) @@ -66,7 +72,7 @@ def dispatch(self, *args, **kwargs): return self._plot(self._obj, *args, **kwargs) -def create_plot_dispatch(function, name): +def create_plot_dispatch(function, name, plot_cls=None): """From a function, creates a dispatch class that will be used by the dispatchers. Parameters @@ -84,6 +90,7 @@ def create_plot_dispatch(function, name): "_plot": staticmethod(function), "__doc__": function.__doc__, "__signature__": inspect.signature(function), + "_plot_class": plot_cls, }, ) @@ -110,19 +117,25 @@ def _get_plotting_func(plot_cls, setting_key): def _plot(obj, *args, **kwargs): return plot_cls(*args, **{setting_key: obj, **kwargs}) - _plot.__doc__ = f"""Builds a {plot_cls.__name__} by setting the value of "{setting_key}" to the current object. + from numpydoc.docscrape import FunctionDoc - Documentation for {plot_cls.__name__} - =========== - {inspect.cleandoc(plot_cls.__doc__) if plot_cls.__doc__ is not None else None} - """ + fdoc = FunctionDoc(plot_cls) + fdoc["Parameters"] = list( + filter(lambda p: p.name.replace(":", "") != setting_key, fdoc["Parameters"]) + ) + docstring = str(fdoc) + docstring = docstring[docstring.find("\n") :].lstrip() + + _plot.__doc__ = f"""Builds a ``{plot_cls.__name__}`` by setting the value of "{setting_key}" to the current object.""" + _plot.__doc__ += "\n\n" + docstring sig = inspect.signature(plot_cls) # The signature will be the same as the plot class, but without the setting key, which # will be added by the _plot function _plot.__signature__ = sig.replace( - parameters=[p for p in sig.parameters.values() if p.name != setting_key] + parameters=[p for p in sig.parameters.values() if p.name != setting_key], + return_annotation=plot_cls, ) return _plot @@ -206,11 +219,53 @@ def register_plotable( plot_handler = getattr(plotable, plot_handler_attr) - plot_dispatch = create_plot_dispatch(plotting_func, name) + plot_dispatch = create_plot_dispatch(plotting_func, name, plot_cls=plot_cls) # Register the function in the plot_handler plot_handler.register(name, plot_dispatch, default=default, **kwargs) +def _get_merged_parameters( + doc1, + doc2, + excludedoc1: list = (), + replacedoc1: dict = {}, + excludedoc2: list = (), + replacedoc2: dict = {}, +): + from numpydoc.docscrape import FunctionDoc + + def filter_and_replace(params, exclude, replace): + filtered = list( + filter(lambda p: p.name.replace(":", "") not in exclude, params) + ) + + replaced = [] + for p in filtered: + name = p.name.replace(":", "") + if name in replace: + p = p.__class__(name=replace[name], type=p.type, desc=p.desc) + print(p.name) + replaced.append(p) + return replaced + + fdoc1 = FunctionDoc(doc1) + + fdoc2 = FunctionDoc(doc2) + fdoc1["Parameters"] = [ + *filter_and_replace(fdoc1["Parameters"], excludedoc1, replacedoc1), + *filter_and_replace(fdoc2["Parameters"], excludedoc2, replacedoc2), + ] + for k in fdoc1: + if k == "Parameters": + continue + fdoc1[k] = fdoc1[k].__class__() + + docstring = str(fdoc1) + docstring = docstring[docstring.find("\n") :].lstrip() + + return docstring + + def register_data_source( data_source_cls, plot_cls, @@ -272,7 +327,9 @@ def register_data_source( new_parameters.extend(list(plot_cls_params.values())) - signature = signature.replace(parameters=new_parameters) + signature = signature.replace( + parameters=new_parameters, return_annotation=plot_cls + ) params_info = { "data_args": data_args, @@ -320,16 +377,30 @@ def _plot( return plot_cls(**{setting_key: data, **bound.arguments, **plot_kwargs}) _plot.__signature__ = signature - doc = f"Read data into {data_source_cls.__name__} and create a {plot_cls.__name__} from it.\n\n" + doc = f"Creates a ``{data_source_cls.__name__}`` object and then plots a ``{plot_cls.__name__}`` from it.\n\n" + + doc += ( + # "This function accepts the arguments for creating both the data source and the plot. The following" + # " arguments of the data source have been renamed so that they don't clash with the plot arguments:\n" + # + "\n".join(f" - {v} -> {k}" for k, v in replaced_data_args.items()) + "\n" + + _get_merged_parameters( + func, + plot_cls, + excludedoc1=(list(inspect.signature(func).parameters)[0],), + replacedoc1={ + v: k for k, v in params_info["replaced_data_args"].items() + }, + excludedoc2=(setting_key,), + ) + ) doc += ( - "This function accepts the arguments for creating both the data source and the plot. The following" - " arguments of the data source have been renamed so that they don't clash with the plot arguments:\n" - + "\n".join(f" - {v} -> {k}" for k, v in replaced_data_args.items()) - + f"\n\nDocumentation for the {data_source_cls.__name__} creator ({func.__name__})" - f"\n=============\n{inspect.cleandoc(func.__doc__) if func.__doc__ is not None else None}" - f"\n\nDocumentation for {plot_cls.__name__}:" - f"\n=============\n{inspect.cleandoc(plot_cls.__doc__) if plot_cls.__doc__ is not None else None}" + "\n\nSee also\n--------\n" + + plot_cls.__name__ + + "\n The plot class used to generate the plot.\n" + + data_source_cls.__name__ + + "\n The class to which data is converted." ) _plot.__doc__ = doc @@ -405,7 +476,7 @@ def register_sile_method( ), } - signature = signature.replace(parameters=new_parameters) + signature = signature.replace(parameters=new_parameters, return_annotation=plot_cls) def _plot(obj, *args, **kwargs): bound = signature.bind_partial(**kwargs) @@ -433,16 +504,30 @@ def _plot(obj, *args, **kwargs): return plot_cls(**{setting_key: data, **bound.arguments, **plot_kwargs}) _plot.__signature__ = signature - doc = f"Calls {method} and creates a {plot_cls.__name__} from its output.\n\n" + doc = ( + f"Calls ``{method}`` and creates a ``{plot_cls.__name__}`` from its output.\n\n" + ) + + doc += ( + # f"This function accepts the arguments both for calling {method} and creating the plot. The following" + # f" arguments of {method} have been renamed so that they don't clash with the plot arguments:\n" + # + "\n".join(f" - {k} -> {v}" for k, v in replaced_data_args.items()) + "\n" + + _get_merged_parameters( + func, + plot_cls, + excludedoc1=(list(inspect.signature(func).parameters)[0],), + replacedoc1={v: k for k, v in params_info["replaced_data_args"].items()}, + excludedoc2=(setting_key,), + ) + ) doc += ( - f"This function accepts the arguments both for calling {method} and creating the plot. The following" - f" arguments of {method} have been renamed so that they don't clash with the plot arguments:\n" - + "\n".join(f" - {k} -> {v}" for k, v in replaced_data_args.items()) - + f"\n\nDocumentation for {method} " - f"\n=============\n{inspect.cleandoc(func.__doc__) if func.__doc__ is not None else None}" - f"\n\nDocumentation for {plot_cls.__name__}:" - f"\n=============\n{inspect.cleandoc(plot_cls.__doc__) if plot_cls.__doc__ is not None else None}" + "\n\nSee also\n--------\n" + + plot_cls.__name__ + + "\n The plot class used to generate the plot.\n" + + method + + "\n The method called to get the data." ) _plot.__doc__ = doc diff --git a/src/sisl/viz/data/pdos.py b/src/sisl/viz/data/pdos.py index 8353821021..fed833b044 100644 --- a/src/sisl/viz/data/pdos.py +++ b/src/sisl/viz/data/pdos.py @@ -6,7 +6,7 @@ from collections.abc import Sequence from pathlib import Path -from typing import Literal, Optional, Union +from typing import Literal, Optional, Tuple, Union import numpy as np from xarray import DataArray @@ -295,14 +295,35 @@ def from_tbtrans( def from_hamiltonian( cls, H: Hamiltonian, - kgrid=None, - kgrid_displ=(0, 0, 0), - Erange=(-2, 2), - E0=0, - nE=100, + kgrid: Tuple[int, int, int] = None, + kgrid_displ: Tuple[float, float, float] = (0, 0, 0), + Erange: Tuple[float, float] = (-2, 2), + E0: float = 0, + nE: int = 100, distribution=get_distribution("gaussian"), ): - """Calculates the PDOS from a sisl Hamiltonian.""" + """Calculates the PDOS from a sisl Hamiltonian. + + Parameters + ---------- + H: + The Hamiltonian from which to calculate the PDOS. + kgrid: + Number of kpoints in each reciprocal space direction. A Monkhorst-pack grid + will be generated from this specification. The PDOS will be averaged over the + whole k-grid. + kgrid_displ: + Displacement of the Monkhorst-Pack grid. + Erange: + Energy range (min and max) for the PDOS calculation. + E0: + Energy shift for the PDOS calculation. + nE: + Number of energy points for the PDOS calculation. + distribution: + The distribution to use for smoothing the PDOS along the energy axis. + Each state will be broadened by this distribution. + """ # Get the kgrid or generate a default grid by checking the interaction between cells # This should probably take into account how big the cell is.