diff --git a/docs/conf.py b/docs/conf.py index c18e66f8d..823e2e6f1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,9 +12,7 @@ # pylint: disable=invalid-name -""" -Sphinx documentation builder -""" +"""Sphinx documentation builder.""" # General options: import inspect @@ -155,6 +153,7 @@ def determine_github_branch() -> str: def linkcode_resolve(domain, info): + """Resolve link.""" if domain != "py": return None diff --git a/pyproject.toml b/pyproject.toml index bb8afbcb1..d79a1f566 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,12 +59,9 @@ style = [ ] lint = [ "qiskit-addon-cutting[style]", - "pydocstyle==6.3.0", "mypy==1.11.2", "reno>=3.4.0", "pylint==3.3.1", - # pydocstyle prefers to parse our pyproject.toml, hence the following line - "toml", ] docs = [ "Sphinx>=3.0.0", @@ -99,6 +96,7 @@ remove-all-unused-imports = true target-version = "py38" [tool.ruff.lint] +extend-select = ["D"] ignore = [ "E501", # line too long ] @@ -107,11 +105,16 @@ ignore = [ "test/**.py" = [ "F405", # star import "F403", # unable to detect undefined names due to star import + "D", # pydocstyle ] "docs/**" = [ "E402", # module level import not at top of file + "D100", # missing docstring in public module ] +[tool.ruff.lint.pydocstyle] +convention = "google" + [tool.pylint.main] py-version = "3.8" disable = "all" diff --git a/qiskit_addon_cutting/__init__.py b/qiskit_addon_cutting/__init__.py index 2864920b7..36dfee2c8 100644 --- a/qiskit_addon_cutting/__init__.py +++ b/qiskit_addon_cutting/__init__.py @@ -9,8 +9,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Circuit Cutting (:mod:`qiskit_addon_cutting`). +"""Circuit Cutting (:mod:`qiskit_addon_cutting`). .. currentmodule:: qiskit_addon_cutting diff --git a/qiskit_addon_cutting/cut_finding/cutting_actions.py b/qiskit_addon_cutting/cut_finding/cutting_actions.py index 476a47479..fe5287af0 100644 --- a/qiskit_addon_cutting/cut_finding/cutting_actions.py +++ b/qiskit_addon_cutting/cut_finding/cutting_actions.py @@ -175,8 +175,7 @@ def next_state_primitive( def get_cost_params( gate_spec: GateSpec, ) -> tuple[float | None, int, float | None]: - """ - Get the cost parameters for gate cuts. + """Get the cost parameters for gate cuts. This method returns a tuple of the form: (, , ) diff --git a/qiskit_addon_cutting/cut_finding/lo_cuts_optimizer.py b/qiskit_addon_cutting/cut_finding/lo_cuts_optimizer.py index 8ae7d977e..70358bd6d 100644 --- a/qiskit_addon_cutting/cut_finding/lo_cuts_optimizer.py +++ b/qiskit_addon_cutting/cut_finding/lo_cuts_optimizer.py @@ -90,21 +90,16 @@ def optimize( """Optimize the cutting of a circuit by calling :meth:`CutOptimization.optimization_pass`. Args: - ``circuit_interface``: defines the circuit to be - cut. This object is then updated with the optimized cuts that - were identified. - - ``optimization_settings``: defines the settings - to be used for the optimization. - - ``device_constraints``: the capabilties of - the target quantum hardware. + circuit_interface: defines the circuit to be cut. This object is then updated + with the optimized cuts that were identified. + optimization_settings: defines the settings to be used for the optimization. + device_constraints: the capabilties of the target quantum hardware. Returns: - The lowest-cost instance of :class:`DisjointSubcircuitsState` - identified in the search, or None if no solution could be found. - In case of the former, the circuit_interface object is also - updated as a side effect to incorporate the cuts found. + The lowest-cost instance of :class:`DisjointSubcircuitsState` + identified in the search, or None if no solution could be found. + In case of the former, the circuit_interface object is also + updated as a side effect to incorporate the cuts found. """ if circuit_interface is not None: self.circuit_interface = circuit_interface diff --git a/qiskit_addon_cutting/cutting_decomposition.py b/qiskit_addon_cutting/cutting_decomposition.py index 9263918e3..f782c8e43 100644 --- a/qiskit_addon_cutting/cutting_decomposition.py +++ b/qiskit_addon_cutting/cutting_decomposition.py @@ -41,8 +41,7 @@ class PartitionedCuttingProblem(NamedTuple): def partition_circuit_qubits( circuit: QuantumCircuit, partition_labels: Sequence[Hashable], inplace: bool = False ) -> QuantumCircuit: - r""" - Replace all nonlocal gates belonging to more than one partition with instances of :class:`.TwoQubitQPDGate`. + r"""Replace all nonlocal gates belonging to more than one partition with instances of :class:`.TwoQubitQPDGate`. :class:`.TwoQubitQPDGate`\ s belonging to a single partition will not be affected. @@ -104,8 +103,7 @@ def partition_circuit_qubits( def cut_gates( circuit: QuantumCircuit, gate_ids: Sequence[int], inplace: bool = False ) -> tuple[QuantumCircuit, list[QPDBasis]]: - r""" - Transform specified gates into :class:`.TwoQubitQPDGate`\ s. + r"""Transform specified gates into :class:`.TwoQubitQPDGate`\ s. Args: circuit: The circuit containing gates to be decomposed @@ -143,8 +141,7 @@ def partition_problem( partition_labels: Sequence[Hashable] | None = None, observables: PauliList | None = None, ) -> PartitionedCuttingProblem: - r""" - Separate an input circuit and observable(s). + r"""Separate an input circuit and observable(s). If ``partition_labels`` is provided, then qubits with matching partition labels will be grouped together, and non-local gates spanning more than one @@ -240,8 +237,7 @@ def partition_problem( def decompose_observables( observables: PauliList, partition_labels: Sequence[Hashable] ) -> dict[Hashable, PauliList]: - """ - Decompose a list of observables with respect to some qubit partition labels. + """Decompose a list of observables with respect to some qubit partition labels. Args: observables: A list of observables to decompose diff --git a/qiskit_addon_cutting/cutting_experiments.py b/qiskit_addon_cutting/cutting_experiments.py index 106609404..1ba15c1c6 100644 --- a/qiskit_addon_cutting/cutting_experiments.py +++ b/qiskit_addon_cutting/cutting_experiments.py @@ -41,8 +41,7 @@ def generate_cutting_experiments( list[QuantumCircuit] | dict[Hashable, list[QuantumCircuit]], list[tuple[float, WeightType]], ]: - r""" - Generate cutting subexperiments and their associated coefficients. + r"""Generate cutting subexperiments and their associated coefficients. If the input, ``circuits``, is a :class:`QuantumCircuit` instance, the output subexperiments will be contained within a 1D array, and ``observables`` is @@ -64,6 +63,7 @@ def generate_cutting_experiments( num_samples: The number of samples to draw from the quasi-probability distribution. If set to infinity, the weights will be generated rigorously rather than by sampling from the distribution. + Returns: A tuple containing the cutting experiments and their associated coefficients. If the input circuits is a :class:`QuantumCircuit` instance, the output subexperiments diff --git a/qiskit_addon_cutting/cutting_reconstruction.py b/qiskit_addon_cutting/cutting_reconstruction.py index 96ec6067a..2dd740c2c 100644 --- a/qiskit_addon_cutting/cutting_reconstruction.py +++ b/qiskit_addon_cutting/cutting_reconstruction.py @@ -38,8 +38,7 @@ def reconstruct_expectation_values( coefficients: Sequence[tuple[float, WeightType]], observables: PauliList | dict[Hashable, PauliList], ) -> list[float]: - r""" - Reconstruct an expectation value from the results of the sub-experiments. + r"""Reconstruct an expectation value from the results of the sub-experiments. Args: results: The results from running the cutting subexperiments. If the cut circuit @@ -174,8 +173,7 @@ def reconstruct_expectation_values( def _process_outcome( cog: CommutingObservableGroup, outcome: int | str, / ) -> np.typing.NDArray[np.float64]: - """ - Process a single outcome of a QPD experiment with observables. + """Process a single outcome of a QPD experiment with observables. Args: cog: The observable set being measured by the current experiment @@ -198,8 +196,7 @@ def _process_outcome( def _process_outcome_v2( cog: CommutingObservableGroup, obs_outcomes: int, qpd_outcomes: int, / ) -> np.typing.NDArray[np.float64]: - """ - Process a single outcome of a QPD experiment with observables. + """Process a single outcome of a QPD experiment with observables. Args: cog: The observable set being measured by the current experiment diff --git a/qiskit_addon_cutting/qpd/decompose.py b/qiskit_addon_cutting/qpd/decompose.py index 035b2101e..c8d0559ab 100644 --- a/qiskit_addon_cutting/qpd/decompose.py +++ b/qiskit_addon_cutting/qpd/decompose.py @@ -32,8 +32,7 @@ def decompose_qpd_instructions( *, inplace: bool = False, ) -> QuantumCircuit: - r""" - Replace all QPD instructions in the circuit with local Qiskit operations and measurements. + r"""Replace all QPD instructions in the circuit with local Qiskit operations and measurements. Args: circuit: The circuit containing QPD instructions @@ -43,6 +42,7 @@ def decompose_qpd_instructions( map_ids: Indices to a specific linear mapping to be applied to the decompositions in the circuit. If no map IDs are provided, the circuit will be decomposed randomly according to the decompositions' joint probability distribution. + inplace: If ``True``, the ``circuit`` will be modified in place. Returns: Circuit which has had all its :class:`BaseQPDGate` instances decomposed into local operations. @@ -127,8 +127,7 @@ def _validate_qpd_instructions( def _decompose_qpd_measurements( circuit: QuantumCircuit, inplace: bool = True ) -> QuantumCircuit: - """ - Create mid-circuit measurements. + """Create mid-circuit measurements. Convert all QPDMeasure instances to Measure instructions. Add any newly created classical bits to a new "qpd_measurements" register. diff --git a/qiskit_addon_cutting/qpd/decompositions.py b/qiskit_addon_cutting/qpd/decompositions.py index 5c0580d9f..512763f12 100644 --- a/qiskit_addon_cutting/qpd/decompositions.py +++ b/qiskit_addon_cutting/qpd/decompositions.py @@ -78,8 +78,7 @@ def g(f): def qpdbasis_from_instruction(gate: Instruction, /) -> QPDBasis: - """ - Generate a :class:`.QPDBasis` object, given a supported operation. + """Generate a :class:`.QPDBasis` object, given a supported operation. All two-qubit gates which implement the :meth:`~qiskit.circuit.Gate.to_matrix` method are supported. This should include the vast majority of gates with no unbound @@ -127,8 +126,7 @@ def qpdbasis_from_instruction(gate: Instruction, /) -> QPDBasis: def _explicitly_supported_instructions() -> set[str]: - """ - Return a set of instruction names with explicit support for automatic decomposition. + """Return a set of instruction names with explicit support for automatic decomposition. These instructions are *explicitly* supported by :func:`qpdbasis_from_instruction`. Other instructions may be supported too, via a KAK decomposition. @@ -140,8 +138,7 @@ def _explicitly_supported_instructions() -> set[str]: def _copy_unique_sublists(lsts: tuple[list, ...], /) -> tuple[list, ...]: - """ - Copy each list in a sequence of lists while preserving uniqueness. + """Copy each list in a sequence of lists while preserving uniqueness. This is useful to ensure that the two sets of ``maps`` in a :class:`QPDBasis` will be independent of each other. This enables one to @@ -158,8 +155,7 @@ def _copy_unique_sublists(lsts: tuple[list, ...], /) -> tuple[list, ...]: def _u_from_thetavec( theta: np.typing.NDArray[np.float64] | Sequence[float], / ) -> np.typing.NDArray[np.complex128]: - r""" - Exponentiate the non-local portion of a KAK decomposition. + r"""Exponentiate the non-local portion of a KAK decomposition. This implements Eq. (6) of https://arxiv.org/abs/2006.11174v2: diff --git a/qiskit_addon_cutting/qpd/instructions/qpd_gate.py b/qiskit_addon_cutting/qpd/instructions/qpd_gate.py index 42f6a8330..a83516936 100644 --- a/qiskit_addon_cutting/qpd/instructions/qpd_gate.py +++ b/qiskit_addon_cutting/qpd/instructions/qpd_gate.py @@ -31,15 +31,14 @@ def __init__( basis_id: int | None = None, label: str | None = None, ): - """ - Initialize the instruction, and assign member variables. + """Initialize the instruction, and assign member variables. Args: name: Name of the QPD gate. - basis: A probabilistic basis to which the gate should be decomposed + basis: A :mod:`.QPDBasis` to which the gate should be decomposed num_qubits: The number of qubits on which the QPD gate acts basis_id: An index to the basis to which the gate should be decomposed. - This index is to basis.maps. + This index is to ``basis.maps``. label: An optional label for the gate """ super().__init__(name, num_qubits, num_clbits=0, params=[], label=label) @@ -51,8 +50,7 @@ def __init__( @property def basis(self) -> QPDBasis: - """ - Quasiprobability decomposition basis. + """Quasiprobability decomposition basis. Returns: The basis to which the gate should be decomposed @@ -64,8 +62,7 @@ def _set_basis(self, basis: QPDBasis) -> None: @property def basis_id(self) -> int | None: - """ - Index to basis used to decompose this gate. + """Index to basis used to decompose this gate. If set to None, a random basis will be chosen during decomposition. @@ -76,8 +73,7 @@ def basis_id(self) -> int | None: @basis_id.setter def basis_id(self, basis_id: int | None) -> None: - """ - Set the index to the basis to which this gate should decompose. + """Set the index to the basis to which this gate should decompose. The index corresponds to self.basis.maps. @@ -110,8 +106,13 @@ def __init__( basis_id: int | None = None, label: str | None = None, ): - """ - Initialize the two qubit QPD gate. + """Initialize the two qubit QPD gate. + + Args: + basis: A :mod:`.QPDBasis` to which the gate should be decomposed + basis_id: An index to the basis to which the gate should be decomposed. + This index is to ``basis.maps``. + label: An optional label for the gate Raises: ValueError: The :class:`QPDBasis` acts on a number of qubits not equal to 2. @@ -145,8 +146,7 @@ def from_instruction(cls, instruction: Instruction, /): class SingleQubitQPDGate(BaseQPDGate): - """ - Single qubit gate to be decomposed using quasiprobability decomposition. + """Single qubit gate to be decomposed using quasiprobability decomposition. This gate could be part of a larger decomposition on many qubits, or it could be a standalone single gate decomposition. @@ -160,13 +160,16 @@ def __init__( basis_id: int | None = None, label: str | None = None, ): - """ - Initialize the single qubit QPD gate, and assign member variables. + """Initialize the single qubit QPD gate, and assign member variables. Args: + basis: A :mod:`.QPDBasis` to which the gate should be decomposed qubit_id: This gate's relative index to the decomposition which it belongs. Single qubit QPDGates should have qubit_id 0 if they describe a local decomposition, such as a wire cut. + basis_id: An index to the basis to which the gate should be decomposed. + This index is to ``basis.maps``. + label: An optional label for the gate Raises: ValueError: qubit_id is out of range diff --git a/qiskit_addon_cutting/qpd/qpd_basis.py b/qiskit_addon_cutting/qpd/qpd_basis.py index e8e8b6b30..f6ccb45d6 100644 --- a/qiskit_addon_cutting/qpd/qpd_basis.py +++ b/qiskit_addon_cutting/qpd/qpd_basis.py @@ -31,8 +31,7 @@ def __init__( maps: Sequence[tuple[Sequence[Instruction], ...]], coeffs: Sequence[float], ): - """ - Assign member variables. + """Assign member variables. Args: maps: A sequence of tuples describing the noisy operations probabilistically @@ -97,8 +96,7 @@ def probabilities(self) -> Sequence[float]: @property def kappa(self) -> float: - """ - Get the square root of the sampling overhead. + """Get the square root of the sampling overhead. This quantity is the sum of the magnitude of the coefficients. """ @@ -106,8 +104,7 @@ def kappa(self) -> float: @property def overhead(self) -> float: - """ - Get the sampling overhead. + """Get the sampling overhead. The sampling overhead is the square of the sum of the magnitude of the coefficients. """ @@ -115,8 +112,7 @@ def overhead(self) -> float: @staticmethod def from_instruction(gate: Instruction, /) -> QPDBasis: - """ - Generate a :class:`.QPDBasis` object, given a supported operation. + """Generate a :class:`.QPDBasis` object, given a supported operation. This static method is provided for convenience; it simply calls :func:`~qpd.decompositions.qpdbasis_from_instruction` under the hood. diff --git a/qiskit_addon_cutting/qpd/weights.py b/qiskit_addon_cutting/qpd/weights.py index a49878686..f94da3dff 100644 --- a/qiskit_addon_cutting/qpd/weights.py +++ b/qiskit_addon_cutting/qpd/weights.py @@ -54,8 +54,7 @@ def __update_running_product_after_increment( state: Sequence[int], coeff_probabilities: Sequence[npt.NDArray[np.float64]], ): - """ - Update the ``running_product`` list after the ``state`` has been incremented. + """Update the ``running_product`` list after the ``state`` has been incremented. This snippet is used twice in :func:`_generate_exact_weights_and_conditional_probabilities_assume_sorted`; @@ -71,8 +70,7 @@ def __update_running_product_after_increment( def _generate_exact_weights_and_conditional_probabilities_assume_sorted( coeff_probabilities: Sequence[npt.NDArray[np.float64]], threshold: float ): - r""" - Determine all exact weights above ``threshold`` and the conditional probabilities necessary to sample efficiently from all other weights. + r"""Determine all exact weights above ``threshold`` and the conditional probabilities necessary to sample efficiently from all other weights. Each yielded element will be a 2-tuple, the first element of which will be a ``state``, represented by a tuple of ``int``\ s. @@ -190,8 +188,7 @@ def _invert_permutation(p): def _generate_exact_weights_and_conditional_probabilities( coeff_probabilities: Sequence[npt.NDArray[np.float64]], threshold: float ): - """ - Generate exact weights and conditional probabilities. + """Generate exact weights and conditional probabilities. This is identical in behavior to :func:`_generate_exact_weights_and_conditional_probabilities_assume_sorted`, @@ -221,8 +218,7 @@ def _generate_exact_weights_and_conditional_probabilities( def generate_qpd_weights( qpd_bases: Sequence[QPDBasis], num_samples: float = 1000 ) -> dict[tuple[int, ...], tuple[float, WeightType]]: - """ - Generate weights from the joint quasiprobability distribution. + """Generate weights from the joint quasiprobability distribution. Each weight whose absolute value is above a threshold of ``1 / num_samples`` will be evaluated exactly. The remaining weights -- those in @@ -384,8 +380,7 @@ def _populate_samples( conditional_probabilities: dict[tuple[int, ...], npt.NDArray[np.float64]], running_state: tuple[int, ...] = (), ) -> None: - """ - Generate random samples from the conditional probabilitity distributions. + """Generate random samples from the conditional probabilitity distributions. Items get populated into the ``random_samples`` dict, rather than returned. diff --git a/qiskit_addon_cutting/utils/__init__.py b/qiskit_addon_cutting/utils/__init__.py index 52546a5b2..eff37f45a 100644 --- a/qiskit_addon_cutting/utils/__init__.py +++ b/qiskit_addon_cutting/utils/__init__.py @@ -9,8 +9,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Utility functions. +"""Utility functions. ================================================================= Bitwise utilities (:mod:`qiskit_addon_cutting.utils.bitwise`) diff --git a/qiskit_addon_cutting/utils/bitwise.py b/qiskit_addon_cutting/utils/bitwise.py index 9cc114213..26fb86be8 100644 --- a/qiskit_addon_cutting/utils/bitwise.py +++ b/qiskit_addon_cutting/utils/bitwise.py @@ -9,8 +9,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Bitwise utilities. +"""Bitwise utilities. .. currentmodule:: qiskit_addon_cutting.utils.bitwise diff --git a/qiskit_addon_cutting/utils/iteration.py b/qiskit_addon_cutting/utils/iteration.py index 4104c0d90..dcd8960be 100644 --- a/qiskit_addon_cutting/utils/iteration.py +++ b/qiskit_addon_cutting/utils/iteration.py @@ -9,8 +9,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Iteration utilities. +"""Iteration utilities. .. currentmodule:: qiskit_addon_cutting.utils.iteration diff --git a/qiskit_addon_cutting/utils/observable_grouping.py b/qiskit_addon_cutting/utils/observable_grouping.py index 9231e7938..bf815f4bc 100644 --- a/qiskit_addon_cutting/utils/observable_grouping.py +++ b/qiskit_addon_cutting/utils/observable_grouping.py @@ -9,8 +9,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Module for conducting Pauli observable grouping. +"""Module for conducting Pauli observable grouping. .. currentmodule:: qiskit_addon_cutting.utils.observable_grouping diff --git a/qiskit_addon_cutting/utils/simulation.py b/qiskit_addon_cutting/utils/simulation.py index 3417d959c..9d9443926 100644 --- a/qiskit_addon_cutting/utils/simulation.py +++ b/qiskit_addon_cutting/utils/simulation.py @@ -9,8 +9,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Simulation of precise measurement outcome probabilities. +"""Simulation of precise measurement outcome probabilities. .. currentmodule:: qiskit_addon_cutting.utils.simulation diff --git a/qiskit_addon_cutting/utils/transforms.py b/qiskit_addon_cutting/utils/transforms.py index bb6dfc264..69805a8bd 100644 --- a/qiskit_addon_cutting/utils/transforms.py +++ b/qiskit_addon_cutting/utils/transforms.py @@ -9,8 +9,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Functions for manipulating quantum circuits. +"""Functions for manipulating quantum circuits. .. currentmodule:: qiskit_addon_cutting.utils.transforms @@ -196,8 +195,7 @@ def _circuit_from_instructions( qubits: Sequence[Qubit], cregs: Iterable[ClassicalRegister], ) -> QuantumCircuit: - """ - Create a circuit from instructions. + """Create a circuit from instructions. This pipeline is designed to pass all the classical register(s) from the uncut circuit to each subcircuit, so we add them here. diff --git a/qiskit_addon_cutting/utils/transpiler_passes.py b/qiskit_addon_cutting/utils/transpiler_passes.py index 1b913f4ef..0d96b407a 100644 --- a/qiskit_addon_cutting/utils/transpiler_passes.py +++ b/qiskit_addon_cutting/utils/transpiler_passes.py @@ -9,8 +9,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -""" -Transpiler passes useful for circuit knitting. +"""Transpiler passes useful for circuit knitting. .. currentmodule:: qiskit_addon_cutting.utils.transpiler_passes diff --git a/tox.ini b/tox.ini index d748393ef..bbd8b98b7 100644 --- a/tox.ini +++ b/tox.ini @@ -26,7 +26,6 @@ commands = nbqa ruff docs/ autoflake --check --quiet --recursive qiskit_addon_cutting/ docs/ test/ black --check qiskit_addon_cutting/ docs/ test/ - pydocstyle qiskit_addon_cutting/ mypy qiskit_addon_cutting/ reno lint pylint -rn qiskit_addon_cutting/ test/