diff --git a/docs/_ext/autodoc_analysis.py b/docs/_ext/autodoc_analysis.py index 259ce8c0fd..b1eeac9dd0 100644 --- a/docs/_ext/autodoc_analysis.py +++ b/docs/_ext/autodoc_analysis.py @@ -17,6 +17,8 @@ from typing import Any from docs._ext.custom_styles.styles import AnalysisDocstring +from docs._ext.custom_styles.option_parser import process_default_options +from qiskit.exceptions import QiskitError from qiskit_experiments.framework.base_analysis import BaseAnalysis from sphinx.application import Sphinx from sphinx.ext.autodoc import ClassDocumenter @@ -38,7 +40,27 @@ def add_content(self, more_content: Any, no_docstring: bool = False) -> None: sourcename = self.get_sourcename() # analysis class doesn't have explicit init method. - class_doc = self.get_doc()[0] + try: + if self.get_doc() is not None: + class_doc, init_doc = self.get_doc() + else: + return + except ValueError: + raise QiskitError( + f"Documentation of {self.fullname} doesn't match with the expected format." + "Please run sphinx build without using the experiment template." + ) + + option_doc = process_default_options( + current_class=self.object, + default_option_method="_default_options", + section_repr="Analysis Options:", + app=self.env.app, + options=self.options, + config=self.env.app.config, + indent=self.content_indent, + ) + init_doc = list(self.process_doc([init_doc])) # format experiment documentation into the analysis style class_doc_parser = AnalysisDocstring( @@ -46,10 +68,13 @@ def add_content(self, more_content: Any, no_docstring: bool = False) -> None: docstring_lines=class_doc, config=self.env.app.config, indent=self.content_indent, + analysis_opts=option_doc, + init=init_doc, ) # write introduction - for i, line in enumerate(self.process_doc(class_doc_parser.generate_class_docs())): + custom_docs = class_doc_parser.generate_class_docs() + for i, line in enumerate(self.process_doc(custom_docs)): self.add_line(line, sourcename, i) self.add_line("", sourcename) diff --git a/docs/_ext/autodoc_experiment.py b/docs/_ext/autodoc_experiment.py index 9d74f026d3..80736c0d43 100644 --- a/docs/_ext/autodoc_experiment.py +++ b/docs/_ext/autodoc_experiment.py @@ -17,6 +17,7 @@ from typing import Any from docs._ext.custom_styles.styles import ExperimentDocstring +from docs._ext.custom_styles.option_parser import process_default_options from qiskit.exceptions import QiskitError from qiskit_experiments.framework.base_experiment import BaseExperiment from sphinx.application import Sphinx @@ -45,27 +46,34 @@ def add_content(self, more_content: Any, no_docstring: bool = False) -> None: return except ValueError: raise QiskitError( - f"Documentation of {self.name} doesn't match with the expected format." + f"Documentation of {self.fullname} doesn't match with the expected format." "Please run sphinx build without using the experiment template." ) + option_doc = process_default_options( + current_class=self.object, + default_option_method="_default_experiment_options", + section_repr="Experiment Options:", + app=self.env.app, + options=self.options, + config=self.env.app.config, + indent=self.content_indent, + ) + init_doc = list(self.process_doc([init_doc])) + # format experiment documentation into the experiment style class_doc_parser = ExperimentDocstring( target_cls=self.object, docstring_lines=class_doc, config=self.env.app.config, indent=self.content_indent, + experiment_opts=option_doc, + init=init_doc, ) # write introduction - for i, line in enumerate(self.process_doc(class_doc_parser.generate_class_docs())): - self.add_line(line, sourcename, i) - self.add_line("", sourcename) - - # write init method documentation - self.add_line(".. rubric:: Initialization", sourcename) - self.add_line("", sourcename) - for i, line in enumerate(self.process_doc([init_doc])): + custom_docs = class_doc_parser.generate_class_docs() + for i, line in enumerate(self.process_doc(custom_docs)): self.add_line(line, sourcename, i) self.add_line("", sourcename) diff --git a/docs/_ext/autodoc_visualization.py b/docs/_ext/autodoc_visualization.py index 203944fd05..ee4adb0670 100644 --- a/docs/_ext/autodoc_visualization.py +++ b/docs/_ext/autodoc_visualization.py @@ -17,6 +17,7 @@ from typing import Any from docs._ext.custom_styles.styles import VisualizationDocstring +from docs._ext.custom_styles.option_parser import process_default_options from qiskit.exceptions import QiskitError from qiskit_experiments.visualization import BasePlotter, BaseDrawer from sphinx.application import Sphinx @@ -41,23 +42,42 @@ def add_content(self, more_content: Any, no_docstring: bool = False) -> None: "Please run sphinx build without using the visualization template." ) + option_doc = process_default_options( + current_class=self.object, + default_option_method="_default_options", + section_repr="Options:", + app=self.env.app, + options=self.options, + config=self.env.app.config, + indent=self.content_indent, + ) + init_doc = list(self.process_doc([init_doc])) + + fig_option_doc = process_default_options( + current_class=self.object, + default_option_method="_default_figure_options", + section_repr="Figure Options:", + app=self.env.app, + options=self.options, + config=self.env.app.config, + indent=self.content_indent, + ) + # format visualization class documentation into the visualization style class_doc_parser = VisualizationDocstring( target_cls=self.object, docstring_lines=class_doc, config=self.env.app.config, indent=self.content_indent, + opts=option_doc, + figure_opts=fig_option_doc, + init=init_doc, ) # write introduction - for i, line in enumerate(self.process_doc(class_doc_parser.generate_class_docs())): - self.add_line(line, sourcename, i) - self.add_line("", sourcename) - - # write init method documentation - self.add_line(".. rubric:: Initialization", sourcename) - self.add_line("", sourcename) - for i, line in enumerate(self.process_doc([init_doc])): + init_doc = list(self.process_doc([init_doc])) + custom_docs = class_doc_parser.generate_class_docs() + for i, line in enumerate(self.process_doc(custom_docs)): self.add_line(line, sourcename, i) self.add_line("", sourcename) diff --git a/docs/_ext/custom_styles/formatter.py b/docs/_ext/custom_styles/formatter.py index 03665b49d7..a218f9476e 100644 --- a/docs/_ext/custom_styles/formatter.py +++ b/docs/_ext/custom_styles/formatter.py @@ -14,7 +14,7 @@ A class that formats documentation sections. """ from typing import List -from .utils import _check_no_indent +from .utils import _check_no_indent, _write_options class DocstringSectionFormatter: @@ -37,7 +37,11 @@ def format_header(self, lines: List[str]) -> List[str]: @_check_no_indent def format_overview(self, lines: List[str]) -> List[str]: """Format overview section.""" - format_lines = [".. rubric:: Overview", ""] + format_lines = [ + "" + ".. rubric:: Overview", + "", + ] format_lines.extend(lines) format_lines.append("") @@ -46,7 +50,10 @@ def format_overview(self, lines: List[str]) -> List[str]: @_check_no_indent def format_reference(self, lines: List[str]) -> List[str]: """Format reference section.""" - format_lines = [".. rubric:: References", ""] + format_lines = [ + ".. rubric:: References", + "", + ] format_lines.extend(lines) format_lines.append("") @@ -64,7 +71,10 @@ def format_warning(self, lines: List[str]) -> List[str]: @_check_no_indent def format_example(self, lines: List[str]) -> List[str]: """Format example section.""" - format_lines = [".. rubric:: Example", ""] + format_lines = [ + ".. rubric:: Example", + "", + ] format_lines.extend(lines) format_lines.append("") @@ -82,7 +92,10 @@ def format_note(self, lines: List[str]) -> List[str]: @_check_no_indent def format_see_also(self, lines: List[str]) -> List[str]: """Format see also section.""" - format_lines = [".. rubric:: See Also", ""] + format_lines = [ + ".. rubric:: See also", + "", + ] format_lines.extend(lines) format_lines.append("") @@ -92,32 +105,8 @@ def format_see_also(self, lines: List[str]) -> List[str]: @_check_no_indent def format_manual(self, lines: List[str]) -> List[str]: """Format user manual section.""" - format_lines = [".. rubric:: User Manual", ""] - format_lines.extend(lines) - format_lines.append("") - - return format_lines - - -class ExperimentSectionFormatter(DocstringSectionFormatter): - """Formatter for experiment class.""" - - @_check_no_indent - def format_analysis_ref(self, lines: List[str]) -> List[str]: - """Format analysis class reference section.""" - format_lines = [".. rubric:: Analysis Class Reference", ""] - format_lines.extend(lines) - format_lines.append("") - - return format_lines - - @_check_no_indent - def format_experiment_opts(self, lines: List[str]) -> List[str]: - """Format experiment options section.""" format_lines = [ - ".. rubric:: Experiment Options", - "", - "These options can be set by the :meth:`set_experiment_options` method.", + ".. rubric:: User manual", "", ] format_lines.extend(lines) @@ -126,12 +115,10 @@ def format_experiment_opts(self, lines: List[str]) -> List[str]: return format_lines @_check_no_indent - def format_analysis_opts(self, lines: List[str]) -> List[str]: - """Format analysis options section.""" + def format_init(self, lines: List[str]) -> List[str]: + """Format user manual section.""" format_lines = [ - ".. rubric:: Analysis Options", - "", - "These options can be set by the :meth:`analysis.set_options` method.", + ".. rubric:: Initialization", "", ] format_lines.extend(lines) @@ -139,13 +126,15 @@ def format_analysis_opts(self, lines: List[str]) -> List[str]: return format_lines + +class ExperimentSectionFormatter(DocstringSectionFormatter): + """Formatter for experiment class.""" + @_check_no_indent - def format_transpiler_opts(self, lines: List[str]) -> List[str]: - """Format transpiler options section.""" + def format_analysis_ref(self, lines: List[str]) -> List[str]: + """Format analysis class reference section.""" format_lines = [ - ".. rubric:: Transpiler Options", - "", - "This option can be set by the :meth:`set_transpile_options` method.", + ".. rubric:: Analysis class reference", "", ] format_lines.extend(lines) @@ -154,15 +143,16 @@ def format_transpiler_opts(self, lines: List[str]) -> List[str]: return format_lines @_check_no_indent - def format_run_opts(self, lines: List[str]) -> List[str]: - """Format run options section.""" + def format_experiment_opts(self, lines: List[str]) -> List[str]: + """Format experiment options section.""" format_lines = [ - ".. rubric:: Backend Run Options", + ".. rubric:: Experiment options", "", - "This option can be set by the :meth:`set_run_options` method.", + "These options can be set by the :meth:`set_experiment_options` method.", "", ] - format_lines.extend(lines) + for line in _write_options(lines, self.indent): + format_lines.append(line) format_lines.append("") return format_lines @@ -175,12 +165,13 @@ class AnalysisSectionFormatter(DocstringSectionFormatter): def format_analysis_opts(self, lines: List[str]) -> List[str]: """Format analysis options section.""" format_lines = [ - ".. rubric:: Run Options", + ".. rubric:: Analysis options", "", "These are the keyword arguments of :meth:`run` method.", "", ] - format_lines.extend(lines) + for line in _write_options(lines, self.indent): + format_lines.append(line) format_lines.append("") return format_lines @@ -189,7 +180,7 @@ def format_analysis_opts(self, lines: List[str]) -> List[str]: def format_fit_model(self, lines: List[str]) -> List[str]: """Format fit model section.""" format_lines = [ - ".. rubric:: Fit Model", + ".. rubric:: Fit model", "", "This is the curve fitting analysis. ", "The following equation(s) are used to represent curve(s).", @@ -204,7 +195,7 @@ def format_fit_model(self, lines: List[str]) -> List[str]: def format_fit_parameters(self, lines: List[str]) -> List[str]: """Format fit parameter section.""" format_lines = [ - ".. rubric:: Fit Parameters", + ".. rubric:: Fit parameters", "", "The following fit parameters are estimated during the analysis.", "", @@ -228,7 +219,8 @@ def format_opts(self, lines: List[str]) -> List[str]: "The following can be set using :meth:`set_options`.", "", ] - format_lines.extend(lines) + for line in _write_options(lines, self.indent): + format_lines.append(line) format_lines.append("") return format_lines @@ -237,12 +229,13 @@ def format_opts(self, lines: List[str]) -> List[str]: def format_figure_opts(self, lines: List[str]) -> List[str]: """Format figure options section.""" format_lines = [ - ".. rubric:: Figure Options", + ".. rubric:: Figure options", "", "The following can be set using :meth:`set_figure_options`.", "", ] - format_lines.extend(lines) + for line in _write_options(lines, self.indent): + format_lines.append(line) format_lines.append("") return format_lines diff --git a/docs/_ext/custom_styles/option_parser.py b/docs/_ext/custom_styles/option_parser.py new file mode 100644 index 0000000000..bb11b5645b --- /dev/null +++ b/docs/_ext/custom_styles/option_parser.py @@ -0,0 +1,216 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021, 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +A class to recursively collect option documentation from current class. +""" + +import copy +import inspect +import re +from typing import Any, Set, List, Tuple +from typing import Type, Optional + +import numpy as np +from sphinx.ext.autodoc import Sphinx, Options as SphinxOptions +from sphinx.ext.napoleon import Config as NapoleonConfig +from sphinx.ext.napoleon import GoogleDocstring + + +_parameter_doc_regex = re.compile(r'(.+?)\(\s*(.*[^\s]+)\s*\):(.*[^\s]+)') + + +class QiskitExperimentsOptionsDocstring(GoogleDocstring): + """GoogleDocstring with updated parameter field formatter. + + This docstring parser may take options mapping in the Sphinx option + and inject :default_val: role to the restructured text to manage default value. + + Since this class overrides a protected member, it might be sensitive to napoleon version. + """ + + def _format_docutils_params( + self, + fields: List[Tuple[str, str, List[str]]], + field_role: str = "param", + type_role: str = "type", + default_role: str = "default_val", + source_role: str = "source", + ) -> List[str]: + lines = [] + for _name, _type, _desc in fields: + _desc = self._strip_empty(_desc) + if any(_desc): + _desc = self._fix_field_desc(_desc)[0] + lines.append(f":{field_role} {_name}: {_desc}") + else: + lines.append(f":{field_role} {_name}: ") + if _type: + lines.append(f":{type_role} {_name}: {_type}") + if "default_opt_values" in self._opt: + value = _value_repr(self._opt["default_opt_values"].get(_name, None)) + lines.append(f":{default_role} {_name}: {value}") + if "desc_sources" in self._opt and _name in self._opt["desc_sources"]: + source = self._opt["desc_sources"][_name] + lines.append(f":{source_role} {_name}: :class:`~.{source}`") + else: + lines.append(f":{source_role} {_name}: Unknown class") + return lines + [""] + + +def process_default_options( + current_class: Type, + default_option_method: str, + section_repr: str, + app: Sphinx, + options: SphinxOptions, + config: NapoleonConfig, + indent: Optional[str] = "", +): + """A helper function to generate docstring for default options.""" + default_clsmethod = getattr(current_class, default_option_method, None) + if not default_clsmethod: + return [] + default_options = default_clsmethod() + target_args = set(default_options.__dict__.keys()) + + descriptions = ["Parameters:"] + desc_sources = {} + for mro_class in inspect.getmro(current_class): + if default_option_method not in mro_class.__dict__: + # Do not directly get method docs from parent class. + continue + default_opts_clsmethod = getattr(mro_class, default_option_method) + parsed_lines, added_args = _flatten_option_docs( + docstring=default_opts_clsmethod.__doc__, + section_repr=section_repr, + target_args=target_args, + ) + for line in parsed_lines: + descriptions.append(indent + line) + for added_arg in added_args: + desc_sources[added_arg] = ".".join([mro_class.__module__, mro_class.__name__]) + target_args.remove(added_arg) + if not target_args: + break + else: + raise Exception( + f"Option documentation for {', '.join(target_args)} is missing or incomplete " + f"for the class {current_class.__name__}. " + "Use Google style docstring. PEP484 type annotations is not supported for options." + ) + + extra_info = { + "default_opt_values": default_options, + "desc_sources": desc_sources, + } + + # Relying on GoogleDocstring to apply typehint automation + _options = options.copy() + _options.update(extra_info) + _config = copy.copy(config) + _config.napoleon_use_param = True + docstring = QiskitExperimentsOptionsDocstring( + docstring=descriptions, + config=_config, + app=app, + obj=current_class, + options=_options, + ) + return docstring.lines() + + +def _flatten_option_docs( + docstring: str, + section_repr: str, + target_args: Optional[Set[str]] = None, +) -> Tuple[List[str], Set[str]]: + """A helper function to convert multi-line description into single line.""" + if not docstring: + return [], set() + + docstring_lines = docstring.splitlines() + + line_ind = 0 + while line_ind < len(docstring_lines): + if section_repr in docstring_lines[line_ind]: + line_ind += 1 + break + line_ind += 1 + else: + return [], set() + + indent = len(docstring_lines[line_ind]) - len(docstring_lines[line_ind].lstrip()) + tmp = "" + parsed_lines = [] + added_args = set() + for line in docstring_lines[line_ind:]: + if line[indent:].startswith(" "): + # Remove linefeed and turn multi-line description into single-line + tmp += " " + line.lstrip() + else: + if tmp: + matched = _parameter_doc_regex.match(tmp) + if not matched: + raise ValueError( + f"Option documentation '{tmp}' doesn't conform to the " + "expected documentation style. " + "Use ' (): ' format." + ) + opt_name = matched.group(1).strip() + if target_args and opt_name in target_args: + parsed_lines.append(tmp.lstrip()) + added_args.add(opt_name) + # Start new line + tmp = line + + return parsed_lines, added_args + + +def _value_repr(value: Any) -> str: + """Get option value representation.""" + max_elems = 5 + + if isinstance(value, str): + return f'``"{value}"``' + if isinstance(value, list): + if len(value) > max_elems: + elm_repr = ", ".join(map(_value_repr, value[:max_elems])) + ", ..." + else: + elm_repr = ", ".join(map(_value_repr, value)) + return f"[{elm_repr}]" + if isinstance(value, tuple): + if len(value) > max_elems: + elm_repr = ", ".join(map(_value_repr, value[:max_elems])) + ", ..." + else: + elm_repr = ", ".join(map(_value_repr, value)) + return f"({elm_repr})" + if isinstance(value, dict): + keys_repr = map(_value_repr, value.keys()) + vals_repr = map(_value_repr, value.items()) + dict_repr = ", ".join([f"{kr}: {vr}" for kr, vr in zip(keys_repr, vals_repr)]) + return f"{{{dict_repr}}}" + if value.__class__.__module__ == "builtins": + return f":obj:`{value}`" + if value.__class__.__module__.startswith("qiskit"): + return f"Instance of :class:`.{value.__class__.__name__}`" + if callable(value): + return f"Callable :func:`{value.__name__}`" + if isinstance(value, np.ndarray): + if len(value) > max_elems: + num_repr = ", ".join(map(str, value[:max_elems])) + ", ..." + else: + num_repr = ", ".join(map(str, value)) + return f"``array({num_repr}, size={len(value)})``" + + repr_generic = repr(value).replace("\n", "") + return f"``{repr_generic}``" diff --git a/docs/_ext/custom_styles/styles.py b/docs/_ext/custom_styles/styles.py index ae9c3b47e5..ad0f378f57 100644 --- a/docs/_ext/custom_styles/styles.py +++ b/docs/_ext/custom_styles/styles.py @@ -21,7 +21,6 @@ from qiskit_experiments.framework.base_analysis import BaseAnalysis from qiskit_experiments.framework.base_experiment import BaseExperiment -from qiskit_experiments.visualization import BaseDrawer, BasePlotter from sphinx.config import Config as SphinxConfig from .formatter import ( @@ -32,19 +31,21 @@ ) from .section_parsers import load_standard_section, load_fit_parameters from .utils import ( - _generate_options_documentation, _generate_analysis_ref, - _format_default_options, + _get_superclass, ) -section_regex = re.compile(r"# section: (?P\S+)") + +_section_regex = re.compile(r"\s*# section:\s*(?P\S+)") class QiskitExperimentDocstring(ABC): """Qiskit Experiment style docstring parser base class.""" # mapping of sections supported by this style to parsing method or function - __sections__ = {} + __sections__ = { + "header": load_standard_section, + } # section formatter __formatter__ = DocstringSectionFormatter @@ -55,6 +56,7 @@ def __init__( docstring_lines: Union[str, List[str]], config: SphinxConfig, indent: str = "", + **extra_sections: List[str], ): """Create new parser and parse formatted docstring.""" @@ -67,54 +69,58 @@ def __init__( self._indent = indent self._config = config - self._parsed_lines = self._classify(lines) + self._parsed_lines = self._classify(lines, **extra_sections) - def _classify(self, docstrings: List[str]) -> Dict[str, List[str]]: + def _classify( + self, + docstring_lines: List[str], + **extra_sections: List[str], + ) -> Dict[str, List[str]]: """Classify formatted docstring into sections.""" sectioned_docstrings = dict() - def add_new_section(section: str, lines: List[str]): - if lines: - parser = self.__sections__[section] - if not parser: + for sec_key, parsed_lines in extra_sections.items(): + if sec_key not in self.__sections__: + raise KeyError( + f"Section key {sec_key} is not a valid Qiskit Experiments extension " + f"section keys. Use one of {','.join(self.__sections__.keys())}." + ) + sectioned_docstrings[sec_key] = parsed_lines + + current_section = "header" + min_indent = sys.maxsize + tmp_lines = [] + for line in docstring_lines: + matched = _section_regex.match(line) + if matched: + # Process previous section + if min_indent < sys.maxsize: + tmp_lines = [_line[min_indent:] for _line in tmp_lines] + parser = self.__sections__[current_section] + sectioned_docstrings[current_section] = parser(tmp_lines) + # Start new line + sec_key = matched["section_key"] + if sec_key not in self.__sections__: raise KeyError( - f"Section {section} is automatically generated section. " - "This section cannot be overridden by class docstring." + f"Section key {sec_key} is not a valid Qiskit Experiments extension " + f"section keys. Use one of {','.join(self.__sections__.keys())}." ) - sectioned_docstrings[section] = parser(temp_lines) - - current_section = list(self.__sections__.keys())[0] - temp_lines = list() - margin = sys.maxsize - for docstring_line in docstrings: - match = re.match(section_regex, docstring_line.strip()) - if match: - section_name = match["section_name"] - if section_name in self.__sections__: - # parse previous section - if margin < sys.maxsize: - temp_lines = [l[margin:] for l in temp_lines] - add_new_section(current_section, temp_lines) - # set new section - current_section = section_name - temp_lines.clear() - margin = sys.maxsize - else: - raise KeyError(f"Section name {section_name} is invalid.") + current_section = sec_key + tmp_lines.clear() + min_indent = sys.maxsize continue - # calculate section indent - if len(docstring_line) > 0 and not docstring_line.isspace(): + if len(line) > 0 and not line.isspace(): # ignore empty line - indent = len(docstring_line) - len(docstring_line.lstrip()) - margin = min(indent, margin) - - temp_lines.append(docstring_line) - - # parse final section - if margin < sys.maxsize: - temp_lines = [l[margin:] for l in temp_lines] - add_new_section(current_section, temp_lines) + indent = len(line) - len(line.lstrip()) + min_indent = min(indent, min_indent) + tmp_lines.append(line) + # Process final section + if tmp_lines: + if min_indent < sys.maxsize: + tmp_lines = [_line[min_indent:] for _line in tmp_lines] + parser = self.__sections__[current_section] + sectioned_docstrings[current_section] = parser(tmp_lines) # add extra section self._extra_sections(sectioned_docstrings) @@ -163,92 +169,32 @@ class ExperimentDocstring(QiskitExperimentDocstring): "reference": load_standard_section, "manual": load_standard_section, "analysis_ref": load_standard_section, - "experiment_opts": None, - "transpiler_opts": None, - "run_opts": None, + "experiment_opts": load_standard_section, "example": load_standard_section, "note": load_standard_section, "see_also": load_standard_section, + "init": load_standard_section, } __formatter__ = ExperimentSectionFormatter - def __init__( - self, - target_cls: BaseExperiment, - docstring_lines: Union[str, List[str]], - config: SphinxConfig, - indent: str = "", - ): - """Create new parser and parse formatted docstring.""" - super().__init__(target_cls, docstring_lines, config, indent) - def _extra_sections(self, sectioned_docstring: Dict[str, List[str]]): """Generate extra sections.""" + current_class = self._target_cls - # add experiment option - exp_option_desc = [] + # add see also for super classes + if "see_also" not in sectioned_docstring: + class_refs = _get_superclass(current_class, BaseExperiment) + if class_refs: + sectioned_docstring["see_also"] = class_refs + # add analysis reference, if nothing described, it copies from parent exp_docs_config = copy.copy(self._config) exp_docs_config.napoleon_custom_sections = [("experiment options", "args")] - exp_option = _generate_options_documentation( - current_class=self._target_cls, - method_name="_default_experiment_options", - config=exp_docs_config, - indent=self._indent, - ) - if exp_option: - exp_option_desc.extend(exp_option) - exp_option_desc.append("") - exp_option_desc.extend( - _format_default_options( - defaults=self._target_cls._default_experiment_options().__dict__, - indent=self._indent, - ) - ) - else: - exp_option_desc.append("No experiment option available for this experiment.") - - sectioned_docstring["experiment_opts"] = exp_option_desc - - # add transpiler option - transpiler_option_desc = [ - "This option is used for circuit optimization. ", - "See the documentation of :func:`qiskit.transpile ` " - "for available options.", - "", - ] - transpiler_option_desc.extend( - _format_default_options( - defaults=self._target_cls._default_transpile_options().__dict__, - indent=self._indent, - ) - ) - - sectioned_docstring["transpiler_opts"] = transpiler_option_desc - - # add run option - run_option_desc = [ - "This option is used for controlling job execution condition. " - "Note that this option is provider dependent. " - "See provider's backend runner API for available options. " - "See the documentation of " - ":meth:`IBMQBackend.run ` " - "for the IBM Quantum Service.", - "", - ] - run_option_desc.extend( - _format_default_options( - defaults=self._target_cls._default_run_options().__dict__, - indent=self._indent, - ) - ) - sectioned_docstring["run_opts"] = run_option_desc - # add analysis reference, if nothing described, it copies from parent if not sectioned_docstring.get("analysis_ref", None): analysis_desc = _generate_analysis_ref( - current_class=self._target_cls, + current_class=current_class, config=exp_docs_config, indent=self._indent, ) @@ -267,51 +213,24 @@ class AnalysisDocstring(QiskitExperimentDocstring): "fit_parameters": load_fit_parameters, "reference": load_standard_section, "manual": load_standard_section, - "analysis_opts": None, + "analysis_opts": load_standard_section, "example": load_standard_section, "note": load_standard_section, "see_also": load_standard_section, + "init": load_standard_section, } __formatter__ = AnalysisSectionFormatter - def __init__( - self, - target_cls: BaseAnalysis, - docstring_lines: Union[str, List[str]], - config: SphinxConfig, - indent: str = "", - ): - """Create new parser and parse formatted docstring.""" - super().__init__(target_cls, docstring_lines, config, indent) - def _extra_sections(self, sectioned_docstring: Dict[str, List[str]]): """Generate extra sections.""" + current_class = self._target_cls - # add analysis option - option_desc = [] - - analysis_docs_config = copy.copy(self._config) - analysis_docs_config.napoleon_custom_sections = [("analysis options", "args")] - analysis_option = _generate_options_documentation( - current_class=self._target_cls, - method_name="_default_options", - config=analysis_docs_config, - indent=self._indent, - ) - if analysis_option: - option_desc.extend(analysis_option) - option_desc.append("") - option_desc.extend( - _format_default_options( - defaults=self._target_cls._default_options().__dict__, - indent=self._indent, - ) - ) - else: - option_desc.append("No option available for this analysis.") - - sectioned_docstring["analysis_opts"] = option_desc + # add see also for super classes + if "see_also" not in sectioned_docstring: + class_refs = _get_superclass(current_class, BaseAnalysis) + if class_refs: + sectioned_docstring["see_also"] = class_refs class VisualizationDocstring(QiskitExperimentDocstring): @@ -323,74 +242,12 @@ class VisualizationDocstring(QiskitExperimentDocstring): "overview": load_standard_section, "reference": load_standard_section, "manual": load_standard_section, - "opts": None, # For standard options - "figure_opts": None, # For figure options + "opts": load_standard_section, # For standard options + "figure_opts": load_standard_section, # For figure options "example": load_standard_section, "note": load_standard_section, "see_also": load_standard_section, + "init": load_standard_section, } __formatter__ = VisualizationSectionFormatter - - def __init__( - self, - target_cls: Union[BaseDrawer, BasePlotter], - docstring_lines: Union[str, List[str]], - config: SphinxConfig, - indent: str = "", - ): - """Create new parser and parse formatted docstring.""" - super().__init__(target_cls, docstring_lines, config, indent) - - def _extra_sections(self, sectioned_docstring: Dict[str, List[str]]): - """Generate extra sections.""" - # add options - option_desc = [] - figure_option_desc = [] - - docs_config = copy.copy(self._config) - docs_config.napoleon_custom_sections = [ - ("options", "args"), - ("figure options", "args"), - ] - - # Generate options docs - option = _generate_options_documentation( - current_class=self._target_cls, - method_name="_default_options", - config=docs_config, - indent=self._indent, - ) - if option: - option_desc.extend(option) - option_desc.append("") - option_desc.extend( - _format_default_options( - defaults=self._target_cls._default_options().__dict__, - indent=self._indent, - ) - ) - else: - option_desc.append("No options available.") - - # Generate figure options docs - figure_option = _generate_options_documentation( - current_class=self._target_cls, - method_name="_default_figure_options", - config=docs_config, - indent=self._indent, - ) - if figure_option: - figure_option_desc.extend(figure_option) - figure_option_desc.append("") - figure_option_desc.extend( - _format_default_options( - defaults=self._target_cls._default_figure_options().__dict__, - indent=self._indent, - ) - ) - else: - figure_option_desc.append("No figure options available.") - - sectioned_docstring["opts"] = option_desc - sectioned_docstring["figure_opts"] = figure_option_desc diff --git a/docs/_ext/custom_styles/utils.py b/docs/_ext/custom_styles/utils.py index bee6be7ecc..467f5c82dd 100644 --- a/docs/_ext/custom_styles/utils.py +++ b/docs/_ext/custom_styles/utils.py @@ -15,7 +15,8 @@ import inspect import re -from typing import List, Tuple, Dict, Any, Callable +import collections +from typing import List, Callable, Type, Iterator from sphinx.config import Config as SphinxConfig from sphinx.ext.napoleon.docstring import GoogleDocstring @@ -24,6 +25,10 @@ from qiskit_experiments.framework import BaseExperiment +_parameter_regex = re.compile(r'(.+?)\(\s*(.*[^\s]+)\s*\):(.*[^\s]+)') +_rest_role_regex = re.compile(r':(.+?) (.+?):\s*(.*[^\s]+)') + + def _trim_empty_lines(docstring_lines: List[str]) -> List[str]: """A helper function to remove redundant line feeds.""" i_start = 0 @@ -39,92 +44,6 @@ def _trim_empty_lines(docstring_lines: List[str]) -> List[str]: return docstring_lines[i_start:i_end] -def _parse_option_field( - docstring: str, - config: SphinxConfig, - target_args: List[str], - indent: str = "", -) -> Tuple[List[str], List[str]]: - """A helper function to extract descriptions of target arguments.""" - - # use GoogleDocstring parameter parser - experiment_option_parser = GoogleDocstring( - docstring=prepare_docstring(docstring, tabsize=len(indent)), config=config - ) - parsed_lines = experiment_option_parser.lines() - - # remove redundant descriptions - param_regex = re.compile(r":(param|type) (?P\S+):") - target_params_description = [] - described_params = set() - valid_line = False - for line in parsed_lines: - is_item = re.match(param_regex, line) - if is_item: - if is_item["pname"] in target_args: - valid_line = True - described_params.add(is_item["pname"]) - else: - valid_line = False - if valid_line: - target_params_description.append(line) - - # find missing parameters - missing = set(target_args) - described_params - - return target_params_description, list(missing) - - -def _generate_options_documentation( - current_class: object, - method_name: str, - target_args: List[str] = None, - config: SphinxConfig = None, - indent: str = "", -) -> List[str]: - """Automatically generate documentation from the default options method.""" - - if current_class == object: - # check if no more base class - raise Exception(f"Option docstring for {', '.join(target_args)} is missing.") - - options_docstring_lines = [] - - default_opts = getattr(current_class, method_name, None) - if not default_opts: - # getter option is not defined - return [] - - if not target_args: - target_args = list(default_opts().__dict__.keys()) - - # parse default options method - parsed_lines, target_args = _parse_option_field( - docstring=default_opts.__doc__ or "", - config=config, - target_args=target_args, - indent=indent, - ) - - if target_args: - # parse parent class method docstring if some arg documentation is missing - parent_parsed_lines = _generate_options_documentation( - current_class=inspect.getmro(current_class)[1], - method_name=method_name, - target_args=target_args, - config=config, - indent=indent, - ) - options_docstring_lines.extend(parent_parsed_lines) - - options_docstring_lines.extend(parsed_lines) - - if options_docstring_lines: - return _trim_empty_lines(options_docstring_lines) - - return options_docstring_lines - - def _generate_analysis_ref( current_class: object, config: SphinxConfig = None, @@ -161,7 +80,7 @@ def _generate_analysis_ref( raise Exception(f"Option docstring for analysis_ref is missing.") analysis_ref_lines = [] - for line in lines[analysis_ref_start + 1 :]: + for line in lines[analysis_ref_start + 1:]: # add lines until hitting to next section if line.startswith("# section:"): break @@ -170,36 +89,6 @@ def _generate_analysis_ref( return analysis_ref_lines -def _format_default_options(defaults: Dict[str, Any], indent: str = "") -> List[str]: - """Format default options to docstring lines.""" - docstring_lines = [ - ".. dropdown:: Default values", - indent + ":animate: fade-in-slide-down", - "", - ] - - if not defaults: - docstring_lines.append(indent + "No default options are set.") - else: - docstring_lines.append(indent + "The following values are set by default.") - docstring_lines.append("") - docstring_lines.append(indent + ".. parsed-literal::") - docstring_lines.append("") - for par, value in defaults.items(): - if callable(value): - if value.__class__.__name__ == "function": - # callback function - value_repr = f"Callable {value.__name__}" - else: - # class instance with call method - value_repr = repr(value) - else: - value_repr = repr(value) - docstring_lines.append(indent * 2 + f"{par:<25} := {value_repr}") - - return docstring_lines - - def _check_no_indent(method: Callable) -> Callable: """Check indent of lines and return if this block is correctly indented.""" @@ -213,3 +102,74 @@ def wraps(self, lines: List[str], *args, **kwargs): return method(self, lines, *args, **kwargs) return wraps + + +def _get_superclass(current_class: Type, base_class: Type = None): + """Get a list of restructured text of super classes of current class.""" + + doc_classes = [] + mro_classes = inspect.getmro(current_class)[1:] + if base_class: + for mro_class in mro_classes: + if issubclass(mro_class, base_class) and mro_class is not base_class: + doc_classes.append(mro_class) + else: + doc_classes.extend(mro_classes) + + lines = [] + for doc_class in doc_classes: + lines.append(f"* Superclass :class:`{doc_class.__module__}.{doc_class.__name__}`") + + return lines + + +def _write_options(lines, indent) -> Iterator: + """A helper function to write options section. + + Consume restructured text of default options with role and create plain sphinx text. + """ + + prev_name = None + params = collections.defaultdict(dict) + tmp = {} + for line in lines: + if len(line) == 0 or line.isspace(): + continue + matched = _rest_role_regex.match(line) + if not matched: + raise ValueError( + f"{line} is not a valid directive. This must be parsed by docstring extension." + ) + role = matched.group(1) + name = matched.group(2) + data = matched.group(3) + if role == "mro_index": + data = int(data) + if prev_name and prev_name != name: + params["Unknown class"][prev_name] = tmp + tmp = {role: data} + prev_name = name + elif role == "source": + params[data][name] = tmp + tmp = {} + prev_name = None + else: + tmp[role] = data + prev_name = name + + if not params: + yield "Option is not provided from this class." + else: + yield "Options" + for source, data in params.items(): + yield indent + f"* Defined in the class {source}:" + yield "" + for name, info in data.items(): + _type = info.get("type", "n/a") + _default = info.get("default_val", "n/a") + _desc = info.get("param", "n/a") + yield indent + f" * **{name}** ({_type})" + yield "" + yield indent + f" | Default value: {_default}" + yield indent + f" | {_desc}" + yield "" diff --git a/docs/conf.py b/docs/conf.py index 919aec8fbc..5b2b90a979 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -160,6 +160,7 @@ "matplotlib": ("https://matplotlib.org/stable/", None), "qiskit": ("https://qiskit.org/documentation/", None), "uncertainties": ("https://pythonhosted.org/uncertainties", None), + "qiskit_ibm_provider": ("https://qiskit.org/documentation/partners/qiskit_ibm_provider", None), } diff --git a/docs/tutorials/getting_started.rst b/docs/tutorials/getting_started.rst index 852f61fe6b..e46bebcbfa 100644 --- a/docs/tutorials/getting_started.rst +++ b/docs/tutorials/getting_started.rst @@ -69,7 +69,7 @@ physical qubit or qubits the circuits will be executed on. The qubits must be gi Python sequence (usually a tuple or a list). .. note:: - Since 0.5.0, using ``qubits`` instead of ``physical_qubits`` or specifying an + Since 0.5.0, using ``qubits`` instead of ``physical_qubits`` or specifying an integer qubit index instead of a one-element sequence for a single-qubit experiment is deprecated. @@ -210,6 +210,8 @@ The actual backend jobs that were executed for the experiment can be accessed wi See the how-tos for :doc:`rerunning the analysis ` for an existing experiment that finished execution. +.. _guide_setting_options: + Setting options for your experiment =================================== @@ -226,11 +228,11 @@ supports can be set: .. jupyter-input:: exp.set_run_options(shots=1000, - meas_level=MeasLevel.CLASSIFIED, - meas_return="avg") + meas_level=MeasLevel.CLASSIFIED) -Consult the documentation of :func:`qiskit.execute_function.execute` or the run method -of your specific backend type for valid options. +Consult the documentation of the run method of your +specific backend type for valid options. +For example, see :meth:`qiskit_ibm_provider.IBMBackend.run` for IBM backends. Transpile options ----------------- diff --git a/qiskit_experiments/curve_analysis/base_curve_analysis.py b/qiskit_experiments/curve_analysis/base_curve_analysis.py index aeb5cdd125..5dabe47b1d 100644 --- a/qiskit_experiments/curve_analysis/base_curve_analysis.py +++ b/qiskit_experiments/curve_analysis/base_curve_analysis.py @@ -160,7 +160,7 @@ def _default_options(cls) -> Options: data_processor (Callable): A callback function to format experiment data. This can be a :class:`.DataProcessor` instance that defines the `self.__call__` method. - normalization (bool) : Set ``True`` to normalize y values within range [-1, 1]. + normalization (bool): Set ``True`` to normalize y values within range [-1, 1]. Default to ``False``. average_method (str): Method to average the y values when the same x values appear multiple times. One of "sample", "iwv" (i.e. inverse weighted variance), diff --git a/qiskit_experiments/framework/base_analysis.py b/qiskit_experiments/framework/base_analysis.py index bc2a9a9b55..53101a0856 100644 --- a/qiskit_experiments/framework/base_analysis.py +++ b/qiskit_experiments/framework/base_analysis.py @@ -87,7 +87,12 @@ def copy(self) -> "BaseAnalysis": @classmethod def _default_options(cls) -> Options: - """Default analysis options common to all analyzes.""" + """Default analysis options common to all analyses. + + Analysis Options: + figure_names (str or List[str]): Identifier of figures that appear in the + experiment data to sort figures by name. + """ options = Options() # figure names can be set for each analysis by calling # experiment_obj.analysis.set_options(figure_names=FIGURE_NAMES) diff --git a/qiskit_experiments/framework/base_experiment.py b/qiskit_experiments/framework/base_experiment.py index 01fe623249..62ff068b1a 100644 --- a/qiskit_experiments/framework/base_experiment.py +++ b/qiskit_experiments/framework/base_experiment.py @@ -378,6 +378,8 @@ def set_transpile_options(self, **fields): Raises: QiskitError: If `initial_layout` is one of the fields. + + .. seealso:: The :ref:`guide_setting_options` guide for code example. """ if "initial_layout" in fields: raise QiskitError( @@ -402,6 +404,8 @@ def set_run_options(self, **fields): Args: fields: The fields to update the options + + .. seealso:: The :ref:`guide_setting_options` guide for code example. """ self._run_options.update_options(**fields) self._set_run_options = self._set_run_options.union(fields) diff --git a/qiskit_experiments/library/calibration/fine_amplitude.py b/qiskit_experiments/library/calibration/fine_amplitude.py index dc1dbb184d..fad3ef344e 100644 --- a/qiskit_experiments/library/calibration/fine_amplitude.py +++ b/qiskit_experiments/library/calibration/fine_amplitude.py @@ -37,9 +37,6 @@ class FineAmplitudeCal(BaseCalibrationExperiment, FineAmplitude): experiment the circuits that are run have a custom gate with the pulse schedule attached to it through the calibrations. - # section: see_also - :class:`.FineAmplitude` - """ @qubit_deprecate() @@ -161,11 +158,7 @@ def update_calibrations(self, experiment_data: ExperimentData): class FineXAmplitudeCal(FineAmplitudeCal): - """A calibration experiment to calibrate the amplitude of the X schedule. - - # section: see_also - :class:`.FineAmplitude` - """ + """A calibration experiment to calibrate the amplitude of the X schedule.""" @qubit_deprecate() def __init__( @@ -214,11 +207,7 @@ def _pre_circuit(self, num_clbits: int) -> QuantumCircuit: class FineSXAmplitudeCal(FineAmplitudeCal): - """A calibration experiment to calibrate the amplitude of the SX schedule. - - # section: see_also - :class:`.FineAmplitude` - """ + """A calibration experiment to calibrate the amplitude of the SX schedule.""" @qubit_deprecate() def __init__( diff --git a/qiskit_experiments/library/calibration/fine_drag_cal.py b/qiskit_experiments/library/calibration/fine_drag_cal.py index 83d0a352bb..f8814ee042 100644 --- a/qiskit_experiments/library/calibration/fine_drag_cal.py +++ b/qiskit_experiments/library/calibration/fine_drag_cal.py @@ -31,11 +31,7 @@ class FineDragCal(BaseCalibrationExperiment, FineDrag): - """A calibration version of the fine drag experiment. - - # section: see_also - :class:`.FineDrag` - """ + """A calibration version of the fine drag experiment.""" @qubit_deprecate() def __init__( @@ -150,11 +146,7 @@ def update_calibrations(self, experiment_data: ExperimentData): class FineXDragCal(FineDragCal): - """Fine drag calibration of X gate. - - # section: see_also - :class:`.FineDrag` - """ + """Fine drag calibration of X gate.""" @qubit_deprecate() def __init__( @@ -187,11 +179,7 @@ def __init__( class FineSXDragCal(FineDragCal): - """Fine drag calibration of X gate. - - # section: see_also - :class:`.FineDrag` - """ + """Fine drag calibration of X gate.""" @qubit_deprecate() def __init__( diff --git a/qiskit_experiments/library/calibration/fine_frequency_cal.py b/qiskit_experiments/library/calibration/fine_frequency_cal.py index f7cb8c8333..049712b7f9 100644 --- a/qiskit_experiments/library/calibration/fine_frequency_cal.py +++ b/qiskit_experiments/library/calibration/fine_frequency_cal.py @@ -29,11 +29,7 @@ class FineFrequencyCal(BaseCalibrationExperiment, FineFrequency): - """A calibration version of the fine frequency experiment. - - # section: see_also - :class:`.FineFrequency` - """ + """A calibration version of the fine frequency experiment.""" @qubit_deprecate() def __init__( diff --git a/qiskit_experiments/library/calibration/frequency_cal.py b/qiskit_experiments/library/calibration/frequency_cal.py index f7ba2696b0..82e52c4eb2 100644 --- a/qiskit_experiments/library/calibration/frequency_cal.py +++ b/qiskit_experiments/library/calibration/frequency_cal.py @@ -28,11 +28,7 @@ class FrequencyCal(BaseCalibrationExperiment, RamseyXY): - """A qubit frequency calibration experiment based on the Ramsey XY experiment. - - # section: see_also - :class:`.RamseyXY` - """ + """A qubit frequency calibration experiment based on the Ramsey XY experiment.""" @qubit_deprecate() def __init__( diff --git a/qiskit_experiments/library/calibration/half_angle_cal.py b/qiskit_experiments/library/calibration/half_angle_cal.py index f78a88a8d1..bc6470873c 100644 --- a/qiskit_experiments/library/calibration/half_angle_cal.py +++ b/qiskit_experiments/library/calibration/half_angle_cal.py @@ -29,11 +29,7 @@ class HalfAngleCal(BaseCalibrationExperiment, HalfAngle): - """Calibration version of the half-angle experiment. - - # section: see_also - :class:`.HalfAngle` - """ + """Calibration version of the half-angle experiment.""" @qubit_deprecate() def __init__( diff --git a/qiskit_experiments/library/calibration/rough_amplitude_cal.py b/qiskit_experiments/library/calibration/rough_amplitude_cal.py index 518b335d8d..0fa8a360b4 100644 --- a/qiskit_experiments/library/calibration/rough_amplitude_cal.py +++ b/qiskit_experiments/library/calibration/rough_amplitude_cal.py @@ -32,11 +32,7 @@ class RoughAmplitudeCal(BaseCalibrationExperiment, Rabi): - """A calibration version of the Rabi experiment. - - # section: see_also - :class:`.Rabi` - """ + """A calibration version of the Rabi experiment.""" @qubit_deprecate() def __init__( @@ -194,11 +190,7 @@ def update_calibrations(self, experiment_data: ExperimentData): class RoughXSXAmplitudeCal(RoughAmplitudeCal): - """A rough amplitude calibration of x and sx gates. - - # section: see_also - :class:`.Rabi`, :class:`.RoughAmplitudeCal` - """ + """A rough amplitude calibration of x and sx gates.""" @qubit_deprecate() def __init__( @@ -228,11 +220,7 @@ def __init__( class EFRoughXSXAmplitudeCal(RoughAmplitudeCal): - """A rough amplitude calibration of x and sx gates on the 1<->2 transition. - - # section: see_also - :class:`.Rabi`, :class:`.RoughAmplitudeCal` - """ + """A rough amplitude calibration of x and sx gates on the 1<->2 transition.""" __outcome__ = "rabi_rate_12" diff --git a/qiskit_experiments/library/calibration/rough_drag_cal.py b/qiskit_experiments/library/calibration/rough_drag_cal.py index aa95365056..2f671937a0 100644 --- a/qiskit_experiments/library/calibration/rough_drag_cal.py +++ b/qiskit_experiments/library/calibration/rough_drag_cal.py @@ -30,9 +30,6 @@ class RoughDragCal(BaseCalibrationExperiment, RoughDrag): """A calibration version of the Drag experiment. - # section: see_also - :class:`.RoughDrag` - # section: manual :ref:`DRAG Calibration` diff --git a/qiskit_experiments/library/calibration/rough_frequency.py b/qiskit_experiments/library/calibration/rough_frequency.py index 4902b02e49..75df488e56 100644 --- a/qiskit_experiments/library/calibration/rough_frequency.py +++ b/qiskit_experiments/library/calibration/rough_frequency.py @@ -28,11 +28,7 @@ class RoughFrequencyCal(BaseCalibrationExperiment, QubitSpectroscopy): - """A calibration experiment that runs QubitSpectroscopy. - - # section: see_also - :class:`.QubitSpectroscopy` - """ + """A calibration experiment that runs QubitSpectroscopy.""" @qubit_deprecate() def __init__( @@ -77,11 +73,7 @@ def _attach_calibrations(self, circuit: QuantumCircuit): class RoughEFFrequencyCal(BaseCalibrationExperiment, EFSpectroscopy): - """A calibration experiment that runs QubitSpectroscopy. - - # section: see_also - :class:`.EFSpectroscopy` - """ + """A calibration experiment that runs QubitSpectroscopy.""" __updater__ = Frequency diff --git a/qiskit_experiments/library/characterization/analysis/cr_hamiltonian_analysis.py b/qiskit_experiments/library/characterization/analysis/cr_hamiltonian_analysis.py index 1fd248fcf5..9fa793600d 100644 --- a/qiskit_experiments/library/characterization/analysis/cr_hamiltonian_analysis.py +++ b/qiskit_experiments/library/characterization/analysis/cr_hamiltonian_analysis.py @@ -42,9 +42,6 @@ class CrossResonanceHamiltonianAnalysis(curve.CompositeCurveAnalysis): where :math:`p_{\beta, |j\rangle}` is a fit parameter of :class:`.BlochTrajectoryAnalysis` for the projection axis :math:`\beta` with the control qubit state :math:`|j\rangle`. - # section: see_also - :class:`.BlochTrajectoryAnalysis` - """ def __init__(self): diff --git a/qiskit_experiments/library/characterization/analysis/local_readout_error_analysis.py b/qiskit_experiments/library/characterization/analysis/local_readout_error_analysis.py index da3c99fdb9..9fc0c25776 100644 --- a/qiskit_experiments/library/characterization/analysis/local_readout_error_analysis.py +++ b/qiskit_experiments/library/characterization/analysis/local_readout_error_analysis.py @@ -56,7 +56,7 @@ def _default_options(cls) -> Options: Analysis Options: plot (bool): Set ``True`` to create figure for fit result. - ax(AxesSubplot): Optional. A matplotlib axis object to draw. + ax (AxesSubplot): Optional. A matplotlib axis object to draw. """ options = super()._default_options() # since the plot size grows exponentially with the number of qubits, plotting is off by default diff --git a/qiskit_experiments/library/characterization/analysis/multi_state_discrimination_analysis.py b/qiskit_experiments/library/characterization/analysis/multi_state_discrimination_analysis.py index 94b9bd990a..e58a5b4b31 100644 --- a/qiskit_experiments/library/characterization/analysis/multi_state_discrimination_analysis.py +++ b/qiskit_experiments/library/characterization/analysis/multi_state_discrimination_analysis.py @@ -51,9 +51,10 @@ def _default_options(cls) -> Options: Analysis Options: plot (bool): Set ``True`` to create figure for fit result. - ax(AxesSubplot): Optional. A matplotlib axis object in which to draw. - discriminator: The discriminator to classify the data. The default is a quadratic - discriminant analysis. + plotter (BasePlotter): A plotter instance to visualize the analysis result. + ax (AxesSubplot): Optional. A matplotlib axis object in which to draw. + discriminator (BaseDiscriminator): The sklearn discriminator to classify the data. + The default is a quadratic discriminant analysis. """ options = super()._default_options() options.plotter = IQPlotter(MplDrawer()) diff --git a/qiskit_experiments/library/characterization/analysis/readout_angle_analysis.py b/qiskit_experiments/library/characterization/analysis/readout_angle_analysis.py index d2c674296d..95b492aca9 100644 --- a/qiskit_experiments/library/characterization/analysis/readout_angle_analysis.py +++ b/qiskit_experiments/library/characterization/analysis/readout_angle_analysis.py @@ -31,7 +31,7 @@ def _default_options(cls) -> Options: Analysis Options: plot (bool): Set ``True`` to create figure for fit result. - ax(AxesSubplot): Optional. A matplotlib axis object to draw. + ax (AxesSubplot): Optional. A matplotlib axis object to draw. """ options = super()._default_options() options.plot = True diff --git a/qiskit_experiments/library/characterization/analysis/resonator_spectroscopy_analysis.py b/qiskit_experiments/library/characterization/analysis/resonator_spectroscopy_analysis.py index 86760195d4..fa2fc44661 100644 --- a/qiskit_experiments/library/characterization/analysis/resonator_spectroscopy_analysis.py +++ b/qiskit_experiments/library/characterization/analysis/resonator_spectroscopy_analysis.py @@ -27,6 +27,13 @@ class ResonatorSpectroscopyAnalysis(curve.ResonanceAnalysis): @classmethod def _default_options(cls): + """Return default analysis options. + + Analysis Options: + dimensionality_reduction (ProjectorType): Type of the data processor node + that will reduce the two-dimensional data to one dimension. + plot_iq_data (bool): Set True to generate IQ plot. + """ options = super()._default_options() options.dimensionality_reduction = ProjectorType.ABS options.result_parameters = [ diff --git a/qiskit_experiments/library/characterization/analysis/t1_analysis.py b/qiskit_experiments/library/characterization/analysis/t1_analysis.py index c1584bfe6f..10a9521e94 100644 --- a/qiskit_experiments/library/characterization/analysis/t1_analysis.py +++ b/qiskit_experiments/library/characterization/analysis/t1_analysis.py @@ -23,12 +23,7 @@ class T1Analysis(curve.DecayAnalysis): - r"""A class to analyze T1 experiments. - - # section: see_also - :class:`.DecayAnalysis` - - """ + """A class to analyze T1 experiments.""" @classmethod def _default_options(cls) -> Options: @@ -74,12 +69,7 @@ def _evaluate_quality(self, fit_data: curve.CurveFitResult) -> Union[str, None]: class T1KerneledAnalysis(curve.DecayAnalysis): - r"""A class to analyze T1 experiments with kerneled data. - - # section: see_also - :class:`.DecayAnalysis` - - """ + """A class to analyze T1 experiments with kerneled data.""" @classmethod def _default_options(cls) -> Options: diff --git a/qiskit_experiments/library/characterization/analysis/t2hahn_analysis.py b/qiskit_experiments/library/characterization/analysis/t2hahn_analysis.py index b9f13b251b..62a1345dfa 100644 --- a/qiskit_experiments/library/characterization/analysis/t2hahn_analysis.py +++ b/qiskit_experiments/library/characterization/analysis/t2hahn_analysis.py @@ -23,12 +23,7 @@ class T2HahnAnalysis(curve.DecayAnalysis): - r"""A class to analyze T2Hahn experiments. - - # section: see_also - :class:`.DecayAnalysis` - - """ + """A class to analyze T2Hahn experiments.""" @classmethod def _default_options(cls) -> Options: diff --git a/qiskit_experiments/library/characterization/analysis/t2ramsey_analysis.py b/qiskit_experiments/library/characterization/analysis/t2ramsey_analysis.py index 5375c3a59c..db58b80669 100644 --- a/qiskit_experiments/library/characterization/analysis/t2ramsey_analysis.py +++ b/qiskit_experiments/library/characterization/analysis/t2ramsey_analysis.py @@ -18,12 +18,7 @@ class T2RamseyAnalysis(curve.DampedOscillationAnalysis): - """T2 Ramsey result analysis class. - - # section: see_also - :class:`.DampedOscillationAnalysis` - - """ + """T2 Ramsey result analysis class.""" @classmethod def _default_options(cls) -> Options: diff --git a/qiskit_experiments/library/characterization/analysis/tphi_analysis.py b/qiskit_experiments/library/characterization/analysis/tphi_analysis.py index 22b42c7567..7aab652b1c 100644 --- a/qiskit_experiments/library/characterization/analysis/tphi_analysis.py +++ b/qiskit_experiments/library/characterization/analysis/tphi_analysis.py @@ -26,13 +26,12 @@ class TphiAnalysis(CompositeAnalysis): - r"""A class to analyze :math:`T_\phi` experiments. # section: see_also - qiskit_experiments.library.characterization.analysis.T1Analysis - qiskit_experiments.library.characterization.analysis.T2HahnAnalysis - qiskit_experiments.library.characterization.analysis.T2RamseyAnalysis + * :py:class:`qiskit_experiments.library.characterization.analysis.T1Analysis` + * :py:class:`qiskit_experiments.library.characterization.analysis.T2HahnAnalysis` + * :py:class:`qiskit_experiments.library.characterization.analysis.T2RamseyAnalysis` """ diff --git a/qiskit_experiments/library/characterization/cr_hamiltonian.py b/qiskit_experiments/library/characterization/cr_hamiltonian.py index d46e84232b..11aeb4bc63 100644 --- a/qiskit_experiments/library/characterization/cr_hamiltonian.py +++ b/qiskit_experiments/library/characterization/cr_hamiltonian.py @@ -198,6 +198,7 @@ def _default_experiment_options(cls) -> Options: """Default experiment options. Experiment Options: + flat_top_widths (np.ndarray): Deprecated. Length of Gaussian flat top to scan. durations (np.ndarray): The total duration of the cross resonance pulse(s) to scan, in units of sec. Values should be longer than pulse ramps. min_durations (int): The minimum default pulse duration in samples. diff --git a/qiskit_experiments/library/characterization/fine_amplitude.py b/qiskit_experiments/library/characterization/fine_amplitude.py index dd25cd0347..e8502c6cd7 100644 --- a/qiskit_experiments/library/characterization/fine_amplitude.py +++ b/qiskit_experiments/library/characterization/fine_amplitude.py @@ -96,8 +96,8 @@ def _default_experiment_options(cls) -> Options: Experiment Options: repetitions (List[int]): A list of the number of times that the gate is repeated. - gate_type (Gate): This is a gate class such as XGate, so that one can obtain a gate - by doing :code:`options.gate_class()`. + gate (Gate): This is a gate class such as XGate, so that one can obtain a gate + by doing :code:`options.gate()`. normalization (bool): If set to True the DataProcessor will normalized the measured signal to the interval [0, 1]. Defaults to True. add_cal_circuits (bool): If set to True then two circuits to calibrate 0 and 1 points diff --git a/qiskit_experiments/library/characterization/fine_drag.py b/qiskit_experiments/library/characterization/fine_drag.py index 0bc93dd5e2..4b9275c6da 100644 --- a/qiskit_experiments/library/characterization/fine_drag.py +++ b/qiskit_experiments/library/characterization/fine_drag.py @@ -128,9 +128,6 @@ class FineDrag(BaseExperiment, RestlessMixin): # section: analysis_ref :class:`.ErrorAmplificationAnalysis` - # section: see_also - :class:`.DragCal` - # section: reference .. ref_arxiv:: 1 1612.00858 .. ref_arxiv:: 2 1011.1949 @@ -245,11 +242,7 @@ def _metadata(self): class FineXDrag(FineDrag): - """Class to fine characterize the DRAG parameter of an X gate. - - # section: see_also - :class:`.FineDrag` - """ + """Class to fine characterize the DRAG parameter of an X gate.""" @qubit_deprecate() def __init__(self, physical_qubits: Sequence[int], backend: Optional[Backend] = None): @@ -275,11 +268,7 @@ def _pre_circuit() -> QuantumCircuit: class FineSXDrag(FineDrag): - """Class to fine characterize the DRAG parameter of an SX gate. - - # section: see_also - :class:`.FineDrag` - """ + """Class to fine characterize the DRAG parameter of an SX gate.""" @qubit_deprecate() def __init__(self, physical_qubits: Sequence[int], backend: Optional[Backend] = None): diff --git a/qiskit_experiments/library/characterization/fine_frequency.py b/qiskit_experiments/library/characterization/fine_frequency.py index b970d7c49e..d280d9fff4 100644 --- a/qiskit_experiments/library/characterization/fine_frequency.py +++ b/qiskit_experiments/library/characterization/fine_frequency.py @@ -91,7 +91,7 @@ def _default_experiment_options(cls) -> Options: Experiment Options: repetitions (List[int]): A list of the number of times that the delay is repeated. - delay_duration (int): The duration of the delay as the number of ``dt``s it contains. + delay_duration (int): The duration of the delay as the number of ``dt`` s it contains. The total length of the delay in units of ``dt`` will be n times ``delay_duration`` where n also determines the rotation angle of the ``RZGate`` by :math:`n \pi/2`. """ diff --git a/qiskit_experiments/library/characterization/resonator_spectroscopy.py b/qiskit_experiments/library/characterization/resonator_spectroscopy.py index 7275412f81..a718e64998 100644 --- a/qiskit_experiments/library/characterization/resonator_spectroscopy.py +++ b/qiskit_experiments/library/characterization/resonator_spectroscopy.py @@ -97,9 +97,6 @@ class ResonatorSpectroscopy(Spectroscopy): # section: analysis_ref :class:`ResonatorSpectroscopyAnalysis` - - # section: see_also - :class:`.QubitSpectroscopy` """ @classmethod diff --git a/qiskit_experiments/library/characterization/t2hahn.py b/qiskit_experiments/library/characterization/t2hahn.py index a86f2095b0..0f624d5a10 100644 --- a/qiskit_experiments/library/characterization/t2hahn.py +++ b/qiskit_experiments/library/characterization/t2hahn.py @@ -70,6 +70,7 @@ def _default_experiment_options(cls) -> Options: Experiment Options: delays (Iterable[float]): Delay times of the experiments. + num_echoes (int): The number of echoes to preform. """ options = super()._default_experiment_options() @@ -94,7 +95,7 @@ def __init__( delays: Total delay times of the experiments. backend: Optional, the backend to run the experiment on. num_echoes: The number of echoes to preform. - backend: Optional, the backend to run the experiment on.. + backend: Optional, the backend to run the experiment on. Raises: QiskitError : Error for invalid input. diff --git a/qiskit_experiments/library/characterization/tphi.py b/qiskit_experiments/library/characterization/tphi.py index 9d38ab64cb..6ded55c881 100644 --- a/qiskit_experiments/library/characterization/tphi.py +++ b/qiskit_experiments/library/characterization/tphi.py @@ -58,9 +58,9 @@ class Tphi(BatchExperiment): :doc:`/manuals/characterization/tphi` # section: see_also - qiskit_experiments.library.characterization.t1 - qiskit_experiments.library.characterization.t2ramsey - qiskit_experiments.library.characterization.t2hahn + * :py:class:`qiskit_experiments.library.characterization.T1` + * :py:class:`qiskit_experiments.library.characterization.T2Ramsey` + * :py:class:`qiskit_experiments.library.characterization.T2Hahn` """ diff --git a/qiskit_experiments/library/quantum_volume/qv_analysis.py b/qiskit_experiments/library/quantum_volume/qv_analysis.py index 4b79eebcb5..d1575309d8 100644 --- a/qiskit_experiments/library/quantum_volume/qv_analysis.py +++ b/qiskit_experiments/library/quantum_volume/qv_analysis.py @@ -48,7 +48,7 @@ def _default_options(cls) -> Options: Analysis Options: plot (bool): Set ``True`` to create figure for fit result. - ax(AxesSubplot): Optional. A matplotlib axis object to draw. + ax (AxesSubplot): Optional. A matplotlib axis object to draw. """ options = super()._default_options() options.plot = True diff --git a/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_experiment.py b/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_experiment.py index fb41a133a5..caeb81d413 100644 --- a/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_experiment.py +++ b/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_experiment.py @@ -89,10 +89,11 @@ def __init__( Clifford samples to shorter sequences. Raises: - QiskitError: If the ``interleaved_element`` is invalid because: - * it has different number of qubits from the qubits argument - * it is not convertible to Clifford object - * it has an invalid delay (e.g. violating the timing constraints of the backend) + QiskitError: When interleaved_element has different number of qubits + from the physical_qubits argument. + QiskitError: When interleaved_element is not convertible to Clifford object. + QiskitError: When interleaved_element has an invalid delay + (e.g. violating the timing constraints of the backend). """ # Validations of interleaved_element # - validate number of qubits of interleaved_element diff --git a/qiskit_experiments/library/randomized_benchmarking/rb_experiment.py b/qiskit_experiments/library/randomized_benchmarking/rb_experiment.py index 7acc79bc64..4f1c0c6dea 100644 --- a/qiskit_experiments/library/randomized_benchmarking/rb_experiment.py +++ b/qiskit_experiments/library/randomized_benchmarking/rb_experiment.py @@ -143,6 +143,9 @@ def _default_experiment_options(cls) -> Options: used to initialize ``numpy.random.default_rng`` when generating circuits. The ``default_rng`` will be initialized with this seed value everytime :meth:`circuits` is called. + full_sampling (bool): If True all Cliffords are independently sampled for + all lengths. If False for sample of lengths longer sequences are constructed + by appending additional Clifford samples to shorter sequences. """ options = super()._default_experiment_options() options.update_options( diff --git a/qiskit_experiments/library/tomography/mit_qpt_experiment.py b/qiskit_experiments/library/tomography/mit_qpt_experiment.py index d477a3a004..710bdfbdc1 100644 --- a/qiskit_experiments/library/tomography/mit_qpt_experiment.py +++ b/qiskit_experiments/library/tomography/mit_qpt_experiment.py @@ -47,8 +47,8 @@ class MitigatedProcessTomography(BatchExperiment): :py:class:`MitigatedTomographyAnalysis` # section: see_also - qiskit_experiments.library.ProcessTomography - qiskit_experiments.library.LocalReadoutError + * :py:class:`qiskit_experiments.library.tomography.ProcessTomography` + * :py:class:`qiskit_experiments.library.characterization.LocalReadoutError` """ diff --git a/qiskit_experiments/library/tomography/mit_qst_experiment.py b/qiskit_experiments/library/tomography/mit_qst_experiment.py index 1a64eefb83..2c129faecb 100644 --- a/qiskit_experiments/library/tomography/mit_qst_experiment.py +++ b/qiskit_experiments/library/tomography/mit_qst_experiment.py @@ -47,8 +47,8 @@ class MitigatedStateTomography(BatchExperiment): :py:class:`MitigatedTomographyAnalysis` # section: see_also - qiskit_experiments.library.StateTomography - qiskit_experiments.library.LocalReadoutError + * :py:class:`qiskit_experiments.library.tomography.StateTomography` + * :py:class:`qiskit_experiments.library.characterization.LocalReadoutError` """ diff --git a/qiskit_experiments/library/tomography/mit_tomography_analysis.py b/qiskit_experiments/library/tomography/mit_tomography_analysis.py index e23e746d65..898313d1ea 100644 --- a/qiskit_experiments/library/tomography/mit_tomography_analysis.py +++ b/qiskit_experiments/library/tomography/mit_tomography_analysis.py @@ -24,7 +24,7 @@ class MitigatedTomographyAnalysis(CompositeAnalysis): Analysis is performed as a :class:`.CompositeAnalysis` consisting of :class:`.LocalReadoutErrorAnalysis` to determine the local - assigment matrices describing single qubit Z-basis readout errors, + assignment matrices describing single qubit Z-basis readout errors, and then these matrices are used to automatically construct a noisy :class:`~.PauliMeasurementBasis` for use during tomographic fitting with the tomography analysis. diff --git a/qiskit_experiments/library/tomography/qpt_analysis.py b/qiskit_experiments/library/tomography/qpt_analysis.py index 1a364c82e9..408927d651 100644 --- a/qiskit_experiments/library/tomography/qpt_analysis.py +++ b/qiskit_experiments/library/tomography/qpt_analysis.py @@ -59,9 +59,6 @@ class ProcessTomographyAnalysis(TomographyAnalysis): # section: reference .. ref_arxiv:: 1 1106.5458 - # section: see_also - :class:`.TomographyAnalysis` - """ @classmethod @@ -69,13 +66,11 @@ def _default_options(cls) -> Options: """Default analysis options Analysis Options: - measurement_basis - (:class:`~qiskit_experiments.library.tomography.basis.MeasurementBasis`): + measurement_basis (:class:`~qiskit_experiments.library.tomography.basis.MeasurementBasis`): The measurement :class:`~qiskit_experiments.library.tomography.basis.MeasurementBasis` to use for tomographic process reconstruction. - preparation_basis - (:class:`~qiskit_experiments.library.tomography.basis.PreparationBasis`): + preparation_basis (:class:`~qiskit_experiments.library.tomography.basis.PreparationBasis`): The preparation :class:`~qiskit_experiments.library.tomography.basis.PreparationBasis` to use for tomographic process reconstruction. @@ -100,9 +95,10 @@ def _default_options(cls) -> Options: This can be a string to select one of the built-in fitters, or a callable to supply a custom fitter function. See the `Fitter Functions` section for additional information. - target (Union[str, :class:`~qiskit.quantum_info.operators.channel.quantum_channel`, - :class:`~qiskit.quantum_info.Operator`]): Optional, Set a custom target quantum - channel for computing the :func:~qiskit.quantum_info.process_fidelity` of the + target (str or + :class:`~qiskit.quantum_info.operators.channel.quantum_channel.QuantumChannel` + or :class:`~qiskit.quantum_info.Operator`): Optional, Set a custom target quantum + channel for computing the :func:`~qiskit.quantum_info.process_fidelity` of the fitted process against (Default: None). conditional_circuit_clbits (list[int]): Optional, the clbit indices in the source circuit to be conditioned on when reconstructing the channel. @@ -114,7 +110,7 @@ def _default_options(cls) -> Options: measurement qubits to used for conditional state reconstruction. Enabling this will return a list of reconstrated channel components conditioned on the remaining tomographic bases conditional on the basis index, and outcome - value for these measurements. The conditionl measurement basis index and + value for these measurements. The conditional measurement basis index and integer value of the measurement outcome is stored in state analysis result extra fields `"conditional_measurement_index"` and `"conditional_measurement_outcome"` respectively. @@ -122,7 +118,7 @@ def _default_options(cls) -> Options: preparation qubits to used for conditional state reconstruction. Enabling this will return a list of reconstrated channel components conditioned on the remaining tomographic bases conditional on the basis index. The - conditionl preparation basis index is stored in state analysis result + conditional preparation basis index is stored in state analysis result extra fields `"conditional_preparation_index"`. """ options = super()._default_options() diff --git a/qiskit_experiments/library/tomography/qpt_experiment.py b/qiskit_experiments/library/tomography/qpt_experiment.py index 125e13e8ad..b109ecf597 100644 --- a/qiskit_experiments/library/tomography/qpt_experiment.py +++ b/qiskit_experiments/library/tomography/qpt_experiment.py @@ -47,9 +47,6 @@ class ProcessTomography(TomographyExperiment): # section: analysis_ref :class:`ProcessTomographyAnalysis` - # section: see_also - :class:`.TomographyExperiment` - """ @deprecate_arguments( diff --git a/qiskit_experiments/library/tomography/qst_analysis.py b/qiskit_experiments/library/tomography/qst_analysis.py index d64982886a..f036a47ec1 100644 --- a/qiskit_experiments/library/tomography/qst_analysis.py +++ b/qiskit_experiments/library/tomography/qst_analysis.py @@ -58,9 +58,6 @@ class StateTomographyAnalysis(TomographyAnalysis): # section: reference .. ref_arxiv:: 1 1106.5458 - # section: see_also - :class:`.TomographyAnalysis` - """ @classmethod @@ -68,8 +65,7 @@ def _default_options(cls) -> Options: """Default analysis options Analysis Options: - measurement_basis - (:class:`~qiskit_experiments.library.tomography.basis.MeasurementBasis`): + measurement_basis (:class:`~qiskit_experiments.library.tomography.basis.MeasurementBasis`): The measurement :class:`~qiskit_experiments.library.tomography.basis.MeasurementBasis` to use for tomographic state reconstruction. @@ -89,9 +85,10 @@ def _default_options(cls) -> Options: measurement_qubits (Sequence[int]): Optional, the physical qubits with tomographic measurements. If not specified will be set to ``[0, ..., N-1]`` for N-qubit tomographic measurements. - target (Union[str, :class:`~qiskit.quantum_info.DensityMatrix`, - :class:`~qiskit.quantum_info.Statevector`]): Optional, et a custom target - quantum state for computing the :func:~qiskit.quantum_info.state_fidelity` + target (str or :class:`~qiskit.quantum_info.DensityMatrix` + or :class:`~qiskit.quantum_info.Statevector`): Optional, + set a custom target quantum state for computing the + :func:`~qiskit.quantum_info.state_fidelity` of the fitted state against (Default: None). conditional_circuit_clbits (list[int]): Optional, the clbit indices in the source circuit to be conditioned on when reconstructing the state. diff --git a/qiskit_experiments/library/tomography/qst_experiment.py b/qiskit_experiments/library/tomography/qst_experiment.py index 24e31f2b1f..53a73417f7 100644 --- a/qiskit_experiments/library/tomography/qst_experiment.py +++ b/qiskit_experiments/library/tomography/qst_experiment.py @@ -45,9 +45,6 @@ class StateTomography(TomographyExperiment): # section: analysis_ref :class:`StateTomographyAnalysis` - # section: see_also - :class:`.TomographyExperiment` - """ @deprecate_arguments( diff --git a/qiskit_experiments/library/tomography/tomography_experiment.py b/qiskit_experiments/library/tomography/tomography_experiment.py index ff96ae4aad..87cb9dad48 100644 --- a/qiskit_experiments/library/tomography/tomography_experiment.py +++ b/qiskit_experiments/library/tomography/tomography_experiment.py @@ -39,11 +39,8 @@ def _default_experiment_options(cls) -> Options: """Default experiment options. Experiment Options: - measurement_basis (:class:`~basis.BaseTomographyMeasurementBasis`): The - Tomography measurement basis to use for the experiment. - The default basis is the :class:`~basis.PauliMeasurementBasis` which - performs measurements in the Pauli Z, X, Y bases for each qubit - measurement. + basis_indices (Iterable[Tuple[List[int], List[int]]]): The basis elements to be measured. + If None All basis elements will be measured. """ options = super()._default_experiment_options()