From 076a6e95177ec8461d2c4fc4d504117bd332022a Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 15 Mar 2023 03:18:45 +0900 Subject: [PATCH 01/14] Remove inherited option description from API doc. --- docs/_ext/custom_styles/styles.py | 2 ++ docs/_ext/custom_styles/utils.py | 32 ++++++++++++++++++++----------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/docs/_ext/custom_styles/styles.py b/docs/_ext/custom_styles/styles.py index 3b37c45c89..454acf6c4a 100644 --- a/docs/_ext/custom_styles/styles.py +++ b/docs/_ext/custom_styles/styles.py @@ -196,6 +196,7 @@ def _extra_sections(self, sectioned_docstring: Dict[str, List[str]]): method_name="_default_experiment_options", config=exp_docs_config, indent=self._indent, + recursive=False, ) if exp_option: exp_option_desc.extend(exp_option) @@ -298,6 +299,7 @@ def _extra_sections(self, sectioned_docstring: Dict[str, List[str]]): method_name="_default_options", config=analysis_docs_config, indent=self._indent, + recursive=False, ) if analysis_option: option_desc.extend(analysis_option) diff --git a/docs/_ext/custom_styles/utils.py b/docs/_ext/custom_styles/utils.py index f86cd250d7..884d321520 100644 --- a/docs/_ext/custom_styles/utils.py +++ b/docs/_ext/custom_styles/utils.py @@ -81,6 +81,7 @@ def _generate_options_documentation( target_args: List[str] = None, config: SphinxConfig = None, indent: str = "", + recursive: bool = True, ) -> List[str]: """Automatically generate documentation from the default options method.""" @@ -107,17 +108,26 @@ def _generate_options_documentation( ) 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) + parent_class = inspect.getmro(current_class)[1] + if recursive: + # parse parent class method docstring if some arg documentation is missing + parent_parsed_lines = _generate_options_documentation( + current_class=parent_class, + 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) + else: + options_docstring_lines.extend(parsed_lines) + clsname = parent_class.__name__ + out = [ + "", + f"See :class:`.{clsname}` for all available options.", + ] + options_docstring_lines.extend(out) if options_docstring_lines: return _trim_empty_lines(options_docstring_lines) From 8602ff511de08a4a7d9f0f6c3db7d1188ac665c3 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 15 Mar 2023 18:12:42 +0900 Subject: [PATCH 02/14] Change logic to show all options in structure based on the class hierarchy. Add missing option documentation. --- docs/_ext/custom_styles/styles.py | 45 +++++----- docs/_ext/custom_styles/utils.py | 86 +++++++++---------- qiskit_experiments/framework/base_analysis.py | 7 +- .../analysis/drag_analysis.py | 6 +- .../multi_state_discrimination_analysis.py | 3 +- .../resonator_spectroscopy_analysis.py | 7 ++ .../characterization/cr_hamiltonian.py | 1 + .../characterization/fine_amplitude.py | 4 +- .../randomized_benchmarking/rb_experiment.py | 3 + .../tomography/tomography_experiment.py | 7 +- 10 files changed, 89 insertions(+), 80 deletions(-) diff --git a/docs/_ext/custom_styles/styles.py b/docs/_ext/custom_styles/styles.py index 461f62e9dc..34af1e909b 100644 --- a/docs/_ext/custom_styles/styles.py +++ b/docs/_ext/custom_styles/styles.py @@ -196,54 +196,57 @@ def _extra_sections(self, sectioned_docstring: Dict[str, List[str]]): method_name="_default_experiment_options", config=exp_docs_config, indent=self._indent, - recursive=False, ) 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, ) ) + exp_option_desc.extend(exp_option) 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 = [] transpiler_option_desc.extend( _format_default_options( defaults=self._target_cls._default_transpile_options().__dict__, indent=self._indent, ) ) - + transpiler_option_desc.extend( + [ + "This option is used for circuit optimization. ", + "See the documentation of :func:`qiskit.transpile ` " + "for available options.", + "", + ] + ) 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 = [] run_option_desc.extend( _format_default_options( defaults=self._target_cls._default_run_options().__dict__, indent=self._indent, ) ) + run_option_desc.extend( + [ + "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.", + "", + ] + ) sectioned_docstring["run_opts"] = run_option_desc # add analysis reference, if nothing described, it copies from parent @@ -299,17 +302,15 @@ def _extra_sections(self, sectioned_docstring: Dict[str, List[str]]): method_name="_default_options", config=analysis_docs_config, indent=self._indent, - recursive=False, ) 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, ) ) + option_desc.extend(analysis_option) else: option_desc.append("No option available for this analysis.") diff --git a/docs/_ext/custom_styles/utils.py b/docs/_ext/custom_styles/utils.py index 77a2707029..8d48ade432 100644 --- a/docs/_ext/custom_styles/utils.py +++ b/docs/_ext/custom_styles/utils.py @@ -15,7 +15,7 @@ import inspect import re -from typing import List, Tuple, Dict, Any, Callable +from typing import List, Set, Tuple, Dict, Any, Callable, Type from sphinx.config import Config as SphinxConfig from sphinx.ext.napoleon.docstring import GoogleDocstring @@ -42,9 +42,9 @@ def _trim_empty_lines(docstring_lines: List[str]) -> List[str]: def _parse_option_field( docstring: str, config: SphinxConfig, - target_args: List[str], + target_args: Set[str], indent: str = "", -) -> Tuple[List[str], List[str]]: +) -> Tuple[List[str], Set[str]]: """A helper function to extract descriptions of target arguments.""" # use GoogleDocstring parameter parser @@ -70,64 +70,56 @@ def _parse_option_field( target_params_description.append(line) # find missing parameters - missing = set(target_args) - described_params + missing = target_args - described_params - return target_params_description, list(missing) + return target_params_description, missing def _generate_options_documentation( - current_class: object, + current_class: Type, method_name: str, target_args: List[str] = None, config: SphinxConfig = None, indent: str = "", - recursive: bool = True, ) -> 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: - parent_class = inspect.getmro(current_class)[1] - if recursive: - # parse parent class method docstring if some arg documentation is missing - parent_parsed_lines = _generate_options_documentation( - current_class=parent_class, - method_name=method_name, - target_args=target_args, - config=config, - indent=indent, + default_opts_clsmethod = getattr(current_class, method_name, None) + if not default_opts_clsmethod: + # getter option is not defined + return [] + target_args = set(default_opts_clsmethod().__dict__.keys()) + + mro_classes = inspect.getmro(current_class) + for i, mro_cls in enumerate(mro_classes): + default_opts_clsmethod = getattr(mro_cls, method_name, None) + if not default_opts_clsmethod: + continue + parsed_lines, target_args = _parse_option_field( + docstring=default_opts_clsmethod.__doc__ or "", + config=config, + target_args=target_args, + indent=indent, + ) + if parsed_lines: + if i == 0: + description = "defined in the current class" + else: + description = "inherited from the parent class" + options_docstring_lines.extend( + [ + f"(Options {description} :class:`.{mro_cls.__name__}`)", + "", + ] ) - options_docstring_lines.extend(parent_parsed_lines) - options_docstring_lines.extend(parsed_lines) - else: options_docstring_lines.extend(parsed_lines) - clsname = parent_class.__name__ - out = [ - "", - f"See :class:`.{clsname}` for all available options.", - ] - options_docstring_lines.extend(out) + if not target_args: + break + else: + # Investigated all parent classes but all args are not described. + raise Exception(f"Option docstring for {', '.join(target_args)} is missing.") if options_docstring_lines: return _trim_empty_lines(options_docstring_lines) @@ -189,7 +181,7 @@ def _format_default_options(defaults: Dict[str, Any], indent: str = "") -> List[ ] if not defaults: - docstring_lines.append(indent + "No default options are set.") + docstring_lines.append(indent + "No default options are set.") else: docstring_lines.append(indent + "The following values are set by default.") docstring_lines.append("") diff --git a/qiskit_experiments/framework/base_analysis.py b/qiskit_experiments/framework/base_analysis.py index bc2a9a9b55..dca146f39a 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 analyzes. + + 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/library/characterization/analysis/drag_analysis.py b/qiskit_experiments/library/characterization/analysis/drag_analysis.py index 96bb6b2fb3..66d7f5a8ab 100644 --- a/qiskit_experiments/library/characterization/analysis/drag_analysis.py +++ b/qiskit_experiments/library/characterization/analysis/drag_analysis.py @@ -80,8 +80,10 @@ class DragCalAnalysis(curve.CurveAnalysis): def _default_options(cls): """Return the default analysis options. - See :meth:`~qiskit_experiment.curve_analysis.CurveAnalysis._default_options` for - descriptions of analysis options. + Analysis Options: + reps (List[int]): The number of times the Rp - Rm gate sequence is repeated in + each series. This option must be provided from the experiment class to + build fit models. """ default_options = super()._default_options() default_options.plotter.set_figure_options( 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..957bb9ec61 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. + plotter (BasePlotter): A plotter instance to visualize the analysis 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. + discriminant analysis. """ options = super()._default_options() options.plotter = IQPlotter(MplDrawer()) 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/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/randomized_benchmarking/rb_experiment.py b/qiskit_experiments/library/randomized_benchmarking/rb_experiment.py index 3daf4e2038..3fe0621cbd 100644 --- a/qiskit_experiments/library/randomized_benchmarking/rb_experiment.py +++ b/qiskit_experiments/library/randomized_benchmarking/rb_experiment.py @@ -138,6 +138,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/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() From 55fbcdc2b171782e6507f104dcb94484e6a1df62 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 15 Mar 2023 18:23:18 +0900 Subject: [PATCH 03/14] More friendly error message --- docs/_ext/custom_styles/utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/_ext/custom_styles/utils.py b/docs/_ext/custom_styles/utils.py index 8d48ade432..282ef3b51f 100644 --- a/docs/_ext/custom_styles/utils.py +++ b/docs/_ext/custom_styles/utils.py @@ -119,7 +119,10 @@ def _generate_options_documentation( break else: # Investigated all parent classes but all args are not described. - raise Exception(f"Option docstring for {', '.join(target_args)} is missing.") + raise Exception( + f"Option documentation for {', '.join(target_args)} is missing " + f"for the class {mro_classes[0].__name__}." + ) if options_docstring_lines: return _trim_empty_lines(options_docstring_lines) From 8cd1224cd116e06667bceecd13b18a0c6af970c2 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 15 Mar 2023 18:33:51 +0900 Subject: [PATCH 04/14] Add missing option documentation. --- qiskit_experiments/library/characterization/t2hahn.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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. From 4dfdc96e68c77cf987589ee35b744bd697437b08 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 15 Mar 2023 19:41:40 +0900 Subject: [PATCH 05/14] Fix missing indent --- docs/_ext/custom_styles/styles.py | 4 ---- docs/_ext/custom_styles/utils.py | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/_ext/custom_styles/styles.py b/docs/_ext/custom_styles/styles.py index 34af1e909b..9c3a753ea8 100644 --- a/docs/_ext/custom_styles/styles.py +++ b/docs/_ext/custom_styles/styles.py @@ -223,7 +223,6 @@ def _extra_sections(self, sectioned_docstring: Dict[str, List[str]]): "This option is used for circuit optimization. ", "See the documentation of :func:`qiskit.transpile ` " "for available options.", - "", ] ) sectioned_docstring["transpiler_opts"] = transpiler_option_desc @@ -244,7 +243,6 @@ def _extra_sections(self, sectioned_docstring: Dict[str, List[str]]): "See the documentation of " ":meth:`IBMQBackend.run ` " "for the IBM Quantum Service.", - "", ] ) sectioned_docstring["run_opts"] = run_option_desc @@ -366,7 +364,6 @@ def _extra_sections(self, sectioned_docstring: Dict[str, List[str]]): ) if option: option_desc.extend(option) - option_desc.append("") option_desc.extend( _format_default_options( defaults=self._target_cls._default_options().__dict__, @@ -385,7 +382,6 @@ def _extra_sections(self, sectioned_docstring: Dict[str, List[str]]): ) 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__, diff --git a/docs/_ext/custom_styles/utils.py b/docs/_ext/custom_styles/utils.py index 282ef3b51f..a21616644a 100644 --- a/docs/_ext/custom_styles/utils.py +++ b/docs/_ext/custom_styles/utils.py @@ -201,6 +201,8 @@ def _format_default_options(defaults: Dict[str, Any], indent: str = "") -> List[ else: value_repr = repr(value) docstring_lines.append(indent * 2 + f"{par:<25} := {value_repr}") + docstring_lines.insert(0, "") + docstring_lines.append("") return docstring_lines From 6c37277780f0204b82bf32805fe174697a41d7f9 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 16 Mar 2023 04:39:19 +0900 Subject: [PATCH 06/14] Fix handling of line feed. Sphinx parser doesn't support line break during the type hint. Long type hint is not shown properly in the API doc. New logic is added to the Sphinx extension to remove these line feeds. --- docs/_ext/custom_styles/utils.py | 70 +++++++++++++------ .../analysis/local_readout_error_analysis.py | 2 +- .../multi_state_discrimination_analysis.py | 6 +- .../analysis/readout_angle_analysis.py | 2 +- .../library/quantum_volume/qv_analysis.py | 2 +- .../library/tomography/qpt_analysis.py | 17 +++-- .../library/tomography/qst_analysis.py | 10 +-- 7 files changed, 66 insertions(+), 43 deletions(-) diff --git a/docs/_ext/custom_styles/utils.py b/docs/_ext/custom_styles/utils.py index a21616644a..2826876773 100644 --- a/docs/_ext/custom_styles/utils.py +++ b/docs/_ext/custom_styles/utils.py @@ -46,33 +46,56 @@ def _parse_option_field( indent: str = "", ) -> Tuple[List[str], Set[str]]: """A helper function to extract descriptions of target arguments.""" + # Convert str into list of reST lines. + rest_doc = prepare_docstring(docstring, tabsize=len(indent)) - # use GoogleDocstring parameter parser - experiment_option_parser = GoogleDocstring( - docstring=prepare_docstring(docstring, tabsize=len(indent)), config=config - ) - parsed_lines = experiment_option_parser.lines() + # Extra formatting to remove line feed and non-target documentation. + line_ind = 0 + while line_ind < len(rest_doc): + if "Options:" in rest_doc[line_ind]: + break + line_ind += 1 + else: + return [], target_args + line0 = rest_doc[line_ind + 1] + section_indent = len(line0) - len(line0.lstrip()) - # remove redundant descriptions - param_regex = re.compile(r":(param|type) (?P\S+):") - target_params_description = [] + to_parse = [] 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) - + tmp = "" + for line in rest_doc[line_ind + 1:]: + if line[section_indent:].startswith(indent): + # Remove line-feed for safety. + # GoogleDocstring parser doesn't support line break during type annotation. + # + # e.g. + # param1 (some_very_long_type1 or some_very_long_type2 + # some_very_long_type2): here is documentation. + # + # Parsing above documentation always fails, and type is not recognized. + tmp += " " + line.lstrip() + else: + if tmp: + argname = tmp.split(maxsplit=1)[0] + if argname in target_args: + # Add only description for target option. Otherwise, just ignore. + described_params.add(argname) + to_parse.append(tmp) + tmp = line # find missing parameters missing = target_args - described_params - return target_params_description, missing + if not to_parse: + return [], missing + + # readd section header, e.g. XXX Options: + to_parse.insert(0, rest_doc[line_ind]) + + # use GoogleDocstring parameter parser + experiment_option_parser = GoogleDocstring(docstring=to_parse, config=config) + parsed_lines = experiment_option_parser.lines() + + return parsed_lines, missing def _generate_options_documentation( @@ -120,8 +143,9 @@ def _generate_options_documentation( else: # Investigated all parent classes but all args are not described. raise Exception( - f"Option documentation for {', '.join(target_args)} is missing " - f"for the class {mro_classes[0].__name__}." + f"Option documentation for {', '.join(target_args)} is missing or incomplete " + f"for the class {mro_classes[0].__name__}. " + "Use Google style docstring. PEP484 type annotations is not supported for options." ) if options_docstring_lines: 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 957bb9ec61..e58a5b4b31 100644 --- a/qiskit_experiments/library/characterization/analysis/multi_state_discrimination_analysis.py +++ b/qiskit_experiments/library/characterization/analysis/multi_state_discrimination_analysis.py @@ -52,9 +52,9 @@ def _default_options(cls) -> Options: Analysis Options: plot (bool): Set ``True`` to create figure for fit result. plotter (BasePlotter): A plotter instance to visualize the analysis 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. + 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/quantum_volume/qv_analysis.py b/qiskit_experiments/library/quantum_volume/qv_analysis.py index c250bf9765..600d2a5252 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/tomography/qpt_analysis.py b/qiskit_experiments/library/tomography/qpt_analysis.py index 1a364c82e9..06e090f005 100644 --- a/qiskit_experiments/library/tomography/qpt_analysis.py +++ b/qiskit_experiments/library/tomography/qpt_analysis.py @@ -69,13 +69,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 +98,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 +113,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 +121,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/qst_analysis.py b/qiskit_experiments/library/tomography/qst_analysis.py index d64982886a..0bec16e9fe 100644 --- a/qiskit_experiments/library/tomography/qst_analysis.py +++ b/qiskit_experiments/library/tomography/qst_analysis.py @@ -68,8 +68,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 +88,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. From 5d019c6b5e550c510d710a81926e525ff491d68e Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 16 Mar 2023 04:58:35 +0900 Subject: [PATCH 07/14] Fix wrong class method owner --- docs/_ext/custom_styles/utils.py | 3 +++ .../randomized_benchmarking/interleaved_rb_experiment.py | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/_ext/custom_styles/utils.py b/docs/_ext/custom_styles/utils.py index 2826876773..0205fccb08 100644 --- a/docs/_ext/custom_styles/utils.py +++ b/docs/_ext/custom_styles/utils.py @@ -117,6 +117,9 @@ def _generate_options_documentation( mro_classes = inspect.getmro(current_class) for i, mro_cls in enumerate(mro_classes): + if method_name not in mro_cls.__dict__: + # Do not directly get method docs from parent class. + continue default_opts_clsmethod = getattr(mro_cls, method_name, None) if not default_opts_clsmethod: continue 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 From 8054f59880cc28f190a1668566c0f91fd69441a8 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 16 Mar 2023 05:49:56 +0900 Subject: [PATCH 08/14] Review suggestions Co-authored-by: Helena Zhang --- docs/_ext/custom_styles/styles.py | 14 +++++--------- docs/conf.py | 1 + docs/tutorials/getting_started.rst | 2 ++ qiskit_experiments/framework/base_analysis.py | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/_ext/custom_styles/styles.py b/docs/_ext/custom_styles/styles.py index 9c3a753ea8..5205df7e4e 100644 --- a/docs/_ext/custom_styles/styles.py +++ b/docs/_ext/custom_styles/styles.py @@ -220,9 +220,8 @@ def _extra_sections(self, sectioned_docstring: Dict[str, List[str]]): ) transpiler_option_desc.extend( [ - "This option is used for circuit optimization. ", - "See the documentation of :func:`qiskit.transpile ` " - "for available options.", + "These options are used for circuit optimization. ", + "See the :ref:`guide_setting_options` guide for code example.", ] ) sectioned_docstring["transpiler_opts"] = transpiler_option_desc @@ -237,12 +236,9 @@ def _extra_sections(self, sectioned_docstring: Dict[str, List[str]]): ) run_option_desc.extend( [ - "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.", + "These options are used for controlling the job execution condition. " + "Note that available options are provider dependent. " + "For example, see :meth:`qiskit_ibm_provider.IBMBackend.run` for IBM backends." ] ) sectioned_docstring["run_opts"] = run_option_desc diff --git a/docs/conf.py b/docs/conf.py index 1085b52310..d50cbf8fde 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -178,6 +178,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 15ada32f8c..e7d7997e1d 100644 --- a/docs/tutorials/getting_started.rst +++ b/docs/tutorials/getting_started.rst @@ -203,6 +203,8 @@ The actual backend jobs that were executed for the experiment can be accessed wi See the how-tos for :doc:`instantiating a new ExperimentData object ` from an existing experiment that finished execution. +.. _guide_setting_options: + Setting experiment options ========================== diff --git a/qiskit_experiments/framework/base_analysis.py b/qiskit_experiments/framework/base_analysis.py index dca146f39a..53101a0856 100644 --- a/qiskit_experiments/framework/base_analysis.py +++ b/qiskit_experiments/framework/base_analysis.py @@ -87,7 +87,7 @@ 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 From f6b8354d57ffac38091e041f20af6c63080a57f6 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 16 Mar 2023 06:04:16 +0900 Subject: [PATCH 09/14] Update visualization docs ordering --- docs/_ext/custom_styles/styles.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_ext/custom_styles/styles.py b/docs/_ext/custom_styles/styles.py index 5205df7e4e..8175f668bb 100644 --- a/docs/_ext/custom_styles/styles.py +++ b/docs/_ext/custom_styles/styles.py @@ -359,13 +359,13 @@ def _extra_sections(self, sectioned_docstring: Dict[str, List[str]]): indent=self._indent, ) if option: - option_desc.extend(option) option_desc.extend( _format_default_options( defaults=self._target_cls._default_options().__dict__, indent=self._indent, ) ) + option_desc.extend(option) else: option_desc.append("No options available.") @@ -377,13 +377,13 @@ def _extra_sections(self, sectioned_docstring: Dict[str, List[str]]): indent=self._indent, ) if figure_option: - figure_option_desc.extend(figure_option) figure_option_desc.extend( _format_default_options( defaults=self._target_cls._default_figure_options().__dict__, indent=self._indent, ) ) + figure_option_desc.extend(figure_option) else: figure_option_desc.append("No figure options available.") From 577ff711ef7cb4c08bebba4fdbc2276395fa1466 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Wed, 22 Mar 2023 00:25:12 +0900 Subject: [PATCH 10/14] Write own docstring parser for default options and add automatic see also injection that references superclasses. --- docs/_ext/autodoc_analysis.py | 29 +- docs/_ext/autodoc_experiment.py | 26 +- docs/_ext/autodoc_visualization.py | 36 ++- docs/_ext/custom_styles/formatter.py | 97 +++--- docs/_ext/custom_styles/option_parser.py | 216 ++++++++++++++ docs/_ext/custom_styles/styles.py | 280 +++++------------- docs/_ext/custom_styles/utils.py | 230 +++++--------- docs/tutorials/getting_started.rst | 6 +- .../curve_analysis/base_curve_analysis.py | 2 +- .../framework/base_experiment.py | 4 + .../library/calibration/fine_amplitude.py | 15 +- .../library/calibration/fine_drag_cal.py | 18 +- .../library/calibration/fine_frequency_cal.py | 6 +- .../library/calibration/frequency_cal.py | 6 +- .../library/calibration/half_angle_cal.py | 6 +- .../calibration/rough_amplitude_cal.py | 18 +- .../library/calibration/rough_drag_cal.py | 3 - .../library/calibration/rough_frequency.py | 12 +- .../analysis/cr_hamiltonian_analysis.py | 3 - .../characterization/analysis/t1_analysis.py | 14 +- .../analysis/t2hahn_analysis.py | 7 +- .../analysis/t2ramsey_analysis.py | 7 +- .../analysis/tphi_analysis.py | 7 +- .../library/characterization/fine_drag.py | 15 +- .../characterization/fine_frequency.py | 2 +- .../resonator_spectroscopy.py | 3 - .../library/characterization/tphi.py | 6 +- .../library/tomography/mit_qpt_experiment.py | 4 +- .../library/tomography/mit_qst_experiment.py | 4 +- .../tomography/mit_tomography_analysis.py | 2 +- .../library/tomography/qpt_analysis.py | 3 - .../library/tomography/qpt_experiment.py | 3 - .../library/tomography/qst_analysis.py | 3 - .../library/tomography/qst_experiment.py | 3 - 34 files changed, 521 insertions(+), 575 deletions(-) create mode 100644 docs/_ext/custom_styles/option_parser.py 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..c10edc078e --- /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): + try: + default_opts_clsmethod = getattr(mro_class, default_option_method) + except AttributeError: + continue + 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 8175f668bb..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,90 +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( - _format_default_options( - defaults=self._target_cls._default_experiment_options().__dict__, - indent=self._indent, - ) - ) - exp_option_desc.extend(exp_option) - 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 = [] - transpiler_option_desc.extend( - _format_default_options( - defaults=self._target_cls._default_transpile_options().__dict__, - indent=self._indent, - ) - ) - transpiler_option_desc.extend( - [ - "These options are used for circuit optimization. ", - "See the :ref:`guide_setting_options` guide for code example.", - ] - ) - sectioned_docstring["transpiler_opts"] = transpiler_option_desc - - # add run option - run_option_desc = [] - run_option_desc.extend( - _format_default_options( - defaults=self._target_cls._default_run_options().__dict__, - indent=self._indent, - ) - ) - run_option_desc.extend( - [ - "These options are used for controlling the job execution condition. " - "Note that available options are provider dependent. " - "For example, see :meth:`qiskit_ibm_provider.IBMBackend.run` for IBM backends." - ] - ) - 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, ) @@ -265,50 +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( - _format_default_options( - defaults=self._target_cls._default_options().__dict__, - indent=self._indent, - ) - ) - option_desc.extend(analysis_option) - 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): @@ -320,72 +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( - _format_default_options( - defaults=self._target_cls._default_options().__dict__, - indent=self._indent, - ) - ) - option_desc.extend(option) - 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( - _format_default_options( - defaults=self._target_cls._default_figure_options().__dict__, - indent=self._indent, - ) - ) - figure_option_desc.extend(figure_option) - 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 0205fccb08..a40db2c12a 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, Set, Tuple, Dict, Any, Callable, Type +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,124 +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: Set[str], - indent: str = "", -) -> Tuple[List[str], Set[str]]: - """A helper function to extract descriptions of target arguments.""" - # Convert str into list of reST lines. - rest_doc = prepare_docstring(docstring, tabsize=len(indent)) - - # Extra formatting to remove line feed and non-target documentation. - line_ind = 0 - while line_ind < len(rest_doc): - if "Options:" in rest_doc[line_ind]: - break - line_ind += 1 - else: - return [], target_args - line0 = rest_doc[line_ind + 1] - section_indent = len(line0) - len(line0.lstrip()) - - to_parse = [] - described_params = set() - tmp = "" - for line in rest_doc[line_ind + 1:]: - if line[section_indent:].startswith(indent): - # Remove line-feed for safety. - # GoogleDocstring parser doesn't support line break during type annotation. - # - # e.g. - # param1 (some_very_long_type1 or some_very_long_type2 - # some_very_long_type2): here is documentation. - # - # Parsing above documentation always fails, and type is not recognized. - tmp += " " + line.lstrip() - else: - if tmp: - argname = tmp.split(maxsplit=1)[0] - if argname in target_args: - # Add only description for target option. Otherwise, just ignore. - described_params.add(argname) - to_parse.append(tmp) - tmp = line - # find missing parameters - missing = target_args - described_params - - if not to_parse: - return [], missing - - # readd section header, e.g. XXX Options: - to_parse.insert(0, rest_doc[line_ind]) - - # use GoogleDocstring parameter parser - experiment_option_parser = GoogleDocstring(docstring=to_parse, config=config) - parsed_lines = experiment_option_parser.lines() - - return parsed_lines, missing - - -def _generate_options_documentation( - current_class: Type, - method_name: str, - target_args: List[str] = None, - config: SphinxConfig = None, - indent: str = "", -) -> List[str]: - """Automatically generate documentation from the default options method.""" - options_docstring_lines = [] - - if not target_args: - default_opts_clsmethod = getattr(current_class, method_name, None) - if not default_opts_clsmethod: - # getter option is not defined - return [] - target_args = set(default_opts_clsmethod().__dict__.keys()) - - mro_classes = inspect.getmro(current_class) - for i, mro_cls in enumerate(mro_classes): - if method_name not in mro_cls.__dict__: - # Do not directly get method docs from parent class. - continue - default_opts_clsmethod = getattr(mro_cls, method_name, None) - if not default_opts_clsmethod: - continue - parsed_lines, target_args = _parse_option_field( - docstring=default_opts_clsmethod.__doc__ or "", - config=config, - target_args=target_args, - indent=indent, - ) - if parsed_lines: - if i == 0: - description = "defined in the current class" - else: - description = "inherited from the parent class" - options_docstring_lines.extend( - [ - f"(Options {description} :class:`.{mro_cls.__name__}`)", - "", - ] - ) - options_docstring_lines.extend(parsed_lines) - if not target_args: - break - else: - # Investigated all parent classes but all args are not described. - raise Exception( - f"Option documentation for {', '.join(target_args)} is missing or incomplete " - f"for the class {mro_classes[0].__name__}. " - "Use Google style docstring. PEP484 type annotations is not supported for options." - ) - - 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, @@ -193,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 @@ -202,38 +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}") - docstring_lines.insert(0, "") - docstring_lines.append("") - - return docstring_lines - - def _check_no_indent(method: Callable) -> Callable: """Check indent of lines and return if this block is correctly indented.""" @@ -247,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"* Super class :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/tutorials/getting_started.rst b/docs/tutorials/getting_started.rst index e7d7997e1d..da568ba3e3 100644 --- a/docs/tutorials/getting_started.rst +++ b/docs/tutorials/getting_started.rst @@ -221,11 +221,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 :meth:`qiskit.execute_function` or the run method of your +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 fcc90cfbd7..58ce56458a 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_experiment.py b/qiskit_experiments/framework/base_experiment.py index f261ff0066..53dd0a885c 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/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 8cbd07fdd2..d1be8c8244 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/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 8f863e9e93..84fd9eb236 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/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/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 06e090f005..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 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 0bec16e9fe..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 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( From cee42e68bd296bade21c33fdbf04ba5a9d180e2f Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 23 Mar 2023 16:06:42 +0900 Subject: [PATCH 11/14] Update docs/_ext/custom_styles/utils.py Co-authored-by: Helena Zhang --- docs/_ext/custom_styles/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_ext/custom_styles/utils.py b/docs/_ext/custom_styles/utils.py index a40db2c12a..dfddd5d0eb 100644 --- a/docs/_ext/custom_styles/utils.py +++ b/docs/_ext/custom_styles/utils.py @@ -118,7 +118,7 @@ def _get_superclass(current_class: Type, base_class: Type = None): lines = [] for doc_class in doc_classes: - lines.append(f"* Super class :class:`{doc_class.__module__}.{doc_class.__name__}`") + lines.append(f"* Superclass :class:`{doc_class.__module__}.{doc_class.__name__}`") return lines From 887b5a7cf4ab8dda190aebc0d5ef5e2e845ff302 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 23 Mar 2023 16:06:56 +0900 Subject: [PATCH 12/14] Update docs/_ext/custom_styles/utils.py Co-authored-by: Helena Zhang --- docs/_ext/custom_styles/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_ext/custom_styles/utils.py b/docs/_ext/custom_styles/utils.py index dfddd5d0eb..467f5c82dd 100644 --- a/docs/_ext/custom_styles/utils.py +++ b/docs/_ext/custom_styles/utils.py @@ -162,7 +162,7 @@ def _write_options(lines, indent) -> Iterator: else: yield "Options" for source, data in params.items(): - yield indent + f"* Defined in the class {source}" + yield indent + f"* Defined in the class {source}:" yield "" for name, info in data.items(): _type = info.get("type", "n/a") From 99c4fe2fe0a00499a3ebf710588d54919ab36232 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Thu, 23 Mar 2023 16:20:57 +0900 Subject: [PATCH 13/14] fix wrong class hierarchy --- docs/_ext/custom_styles/option_parser.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/_ext/custom_styles/option_parser.py b/docs/_ext/custom_styles/option_parser.py index c10edc078e..bb11b5645b 100644 --- a/docs/_ext/custom_styles/option_parser.py +++ b/docs/_ext/custom_styles/option_parser.py @@ -86,10 +86,10 @@ def process_default_options( descriptions = ["Parameters:"] desc_sources = {} for mro_class in inspect.getmro(current_class): - try: - default_opts_clsmethod = getattr(mro_class, default_option_method) - except AttributeError: + 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, From bb72278ba02ac9f6b9be781f66d5ea3238407087 Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Fri, 24 Mar 2023 01:58:58 +0900 Subject: [PATCH 14/14] readd missing ref --- docs/tutorials/getting_started.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/tutorials/getting_started.rst b/docs/tutorials/getting_started.rst index afda6860b6..e46bebcbfa 100644 --- a/docs/tutorials/getting_started.rst +++ b/docs/tutorials/getting_started.rst @@ -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 ===================================