diff --git a/qiskit/circuit/controlledgate.py b/qiskit/circuit/controlledgate.py index a7bf41c6e481..14772cca170f 100644 --- a/qiskit/circuit/controlledgate.py +++ b/qiskit/circuit/controlledgate.py @@ -111,6 +111,20 @@ def __init__( self.ctrl_state = ctrl_state self._name = name + def __repr__(self) -> str: + """Return a representation of the ControlledGate. + + Returns: + A string representation showing the gate's class name, name, label (if set), + number of control qubits, control state, and parameters. + """ + label_str = f" labeled '{self._label}'" if self._label is not None else "" + return ( + f"<{self.__class__.__name__} '{self._name}'{label_str} with " + f"{self._num_ctrl_qubits} control qubits, control state = {self._ctrl_state} " + f"and params={self.params}>" + ) + @property def definition(self) -> QuantumCircuit: """Return definition in terms of other basic gates. If the gate has diff --git a/qiskit/circuit/gate.py b/qiskit/circuit/gate.py index 11e5a105de89..098df3e731b2 100644 --- a/qiskit/circuit/gate.py +++ b/qiskit/circuit/gate.py @@ -46,6 +46,19 @@ def __init__( # Set higher priority than Numpy array and matrix classes __array_priority__ = 20 + def __repr__(self) -> str: + """Return a representation of the Gate. + + Returns: + A string representation showing the gate's class name, name, label (if set), + number of qubits, number of classical bits, and parameters. + """ + label_str = f" labeled '{self.label}'" if self.label is not None else "" + return ( + f"<{self.__class__.__name__} '{self.name}'{label_str} with " + f"{self.num_qubits} qubits, {self.num_clbits} clbits and params={self.params}>" + ) + def to_matrix(self) -> np.ndarray: """Return a Numpy.array for the gate unitary matrix. diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index de95895cc448..6a9ab0f6ab15 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -1559,6 +1559,19 @@ def metadata(self, metadata: dict): raise TypeError("Only a dictionary is accepted for circuit metadata") self._metadata = metadata + def __repr__(self) -> str: + """Return a representation of the QuantumCircuit. + + Returns: + A string representation showing the circuit's name, number of qubits, + number of classical bits, number of instructions, and global phase. + """ + return ( + f"" + ) + def __str__(self) -> str: return str(self.draw(output="text")) diff --git a/qiskit/primitives/statevector_estimator.py b/qiskit/primitives/statevector_estimator.py index 0863e25717e1..e5dec38b64da 100644 --- a/qiskit/primitives/statevector_estimator.py +++ b/qiskit/primitives/statevector_estimator.py @@ -123,6 +123,17 @@ def __init__( self._default_precision = default_precision self._seed = seed + def __repr__(self) -> str: + """Return a representation of the StatevectorEstimator. + + Returns: + A string representation showing the default precision and seed information. + """ + return ( + f"<{self.__class__.__name__} with " + f"default_precision={self._default_precision}, seed={self._seed}>" + ) + @property def default_precision(self) -> float: """Return the default precision""" diff --git a/qiskit/primitives/statevector_sampler.py b/qiskit/primitives/statevector_sampler.py index c154a08884cd..923e94f0c7b1 100644 --- a/qiskit/primitives/statevector_sampler.py +++ b/qiskit/primitives/statevector_sampler.py @@ -143,6 +143,17 @@ def __init__(self, *, default_shots: int = 1024, seed: np.random.Generator | int self._default_shots = default_shots self._seed = seed + def __repr__(self) -> str: + """Return a representation of the StatevectorSampler. + + Returns: + A string representation showing the default shots and seed information. + """ + return ( + f"<{self.__class__.__name__} with " + f"default_shots={self._default_shots}, seed={self._seed}>" + ) + @property def default_shots(self) -> int: """Return the default shots""" diff --git a/qiskit/transpiler/passmanager.py b/qiskit/transpiler/passmanager.py index 93f08dfc89e8..0aebc26442b4 100644 --- a/qiskit/transpiler/passmanager.py +++ b/qiskit/transpiler/passmanager.py @@ -54,6 +54,33 @@ def __init__( max_iteration=max_iteration, ) + def __repr__(self) -> str: + """Return a representation of the PassManager. + + Returns: + A string representation showing the number of pass sets, total passes, + max iterations, and property information. + """ + if self.property_set is None or len(self.property_set) == 0: + properties = "0 properties" + else: + prop_keys = list(self.property_set.keys()) + nprops = len(prop_keys) + if nprops > 3: + _plist = "', '".join(prop_keys[0:3]) + "', ..." + else: + _plist = "', '".join(prop_keys) + "'" + properties = f"{nprops} properties ('{_plist})" + + # _tasks is a list of lists of Task objects + npass = sum(len(task_list) for task_list in self._tasks) + + return ( + f"<{type(self).__name__} with " + f"{len(self._tasks)} sets, {npass} passes, " + f"{self.max_iteration} max iterations, and {properties}>" + ) + def _passmanager_frontend( self, input_program: QuantumCircuit, diff --git a/releasenotes/notes/add-repr-methods-a9676b9f3162bd4a.yaml b/releasenotes/notes/add-repr-methods-a9676b9f3162bd4a.yaml new file mode 100644 index 000000000000..44909977bce6 --- /dev/null +++ b/releasenotes/notes/add-repr-methods-a9676b9f3162bd4a.yaml @@ -0,0 +1,73 @@ +--- +features_circuits: + - | + Added ``__repr__`` methods to :class:`~qiskit.circuit.Gate` and + :class:`~qiskit.circuit.ControlledGate` to provide more informative + string representations when inspecting gate objects in interactive + environments. + + The :class:`~qiskit.circuit.Gate` representation shows the gate name, + label (if set), number of qubits/clbits, and parameters:: + + from qiskit.circuit.library import HGate + + h_gate = HGate(label='my_hadamard') + print(repr(h_gate)) + # Output: + + The :class:`~qiskit.circuit.ControlledGate` representation shows the gate + name, label (if set), number of control qubits, control state, and + parameters:: + + from qiskit.circuit.library import CXGate + + cx_gate = CXGate(label='my_cnot') + print(repr(cx_gate)) + # Output: + + Added ``__repr__`` method to :class:`~qiskit.circuit.QuantumCircuit` to + provide a concise summary of the circuit:: + + from qiskit.circuit import QuantumCircuit + + qc = QuantumCircuit(2, 2, name='Bell') + qc.h(0) + qc.cx(0, 1) + print(repr(qc)) + # Output: + +features_primitives: + - | + Added ``__repr__`` methods to :class:`~qiskit.primitives.StatevectorSampler` + and :class:`~qiskit.primitives.StatevectorEstimator` to show their + configuration:: + + from qiskit.primitives import StatevectorSampler, StatevectorEstimator + + sampler = StatevectorSampler(default_shots=2048, seed=42) + print(repr(sampler)) + # Output: + + estimator = StatevectorEstimator(default_precision=0.01, seed=123) + print(repr(estimator)) + # Output: + +features_transpiler: + - | + Added ``__repr__`` method to :class:`~qiskit.transpiler.PassManager` to + show the number of pass sets, total passes, max iterations, and property + information:: + + from qiskit.transpiler import PassManager + from qiskit.transpiler.passes import Optimize1qGates + + pm = PassManager(Optimize1qGates(), max_iteration=5) + print(repr(pm)) + # Output: + + These representations use the ```` format to provide + useful debugging information without attempting to be eval-able, which + is more practical for complex objects. + + This addresses issue `#8594 `__. + diff --git a/test/python/circuit/test_gate_repr.py b/test/python/circuit/test_gate_repr.py new file mode 100644 index 000000000000..787de77ea14b --- /dev/null +++ b/test/python/circuit/test_gate_repr.py @@ -0,0 +1,82 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2024. +# +# 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 https://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. + +"""Test __repr__ methods for Gate and ControlledGate classes.""" + +from qiskit.circuit import Parameter +from qiskit.circuit.library import HGate, CXGate, RXGate, CCXGate +from test import QiskitTestCase + + +class TestGateRepr(QiskitTestCase): + """Tests for Gate.__repr__ method.""" + + def test_gate_repr_basic(self): + """Test basic Gate repr without label.""" + gate = HGate() + result = repr(gate) + # Use actual class name since some gates are singletons + expected = f"<{gate.__class__.__name__} 'h' with 1 qubits, 0 clbits and params=[]>" + self.assertEqual(result, expected) + + def test_gate_repr_with_label(self): + """Test Gate repr with label.""" + gate = HGate(label="my_hadamard") + result = repr(gate) + expected = "" + self.assertEqual(result, expected) + + def test_gate_repr_with_params(self): + """Test Gate repr with parameters.""" + gate = RXGate(1.57) + result = repr(gate) + # Build expected string using the actual params from the gate + expected = f"" + self.assertEqual(result, expected) + + def test_gate_repr_with_parameter_expression(self): + """Test Gate repr with Parameter expression.""" + theta = Parameter("theta") + gate = RXGate(theta) + result = repr(gate) + # Build expected string using the actual params from the gate + expected = f"" + self.assertEqual(result, expected) + + +class TestControlledGateRepr(QiskitTestCase): + """Tests for ControlledGate.__repr__ method.""" + + def test_controlled_gate_repr_basic(self): + """Test basic ControlledGate repr without label.""" + gate = CXGate() + result = repr(gate) + # Use actual class name since some gates are singletons + expected = f"<{gate.__class__.__name__} 'cx' with 1 control qubits, control state = 1 and params=[]>" + self.assertEqual(result, expected) + + def test_controlled_gate_repr_with_label(self): + """Test ControlledGate repr with label.""" + gate = CXGate(label="my_cnot") + result = repr(gate) + expected = ( + "" + ) + self.assertEqual(result, expected) + + def test_controlled_gate_repr_multiple_controls(self): + """Test ControlledGate repr with multiple control qubits.""" + gate = CCXGate() + result = repr(gate) + # Use actual class name since some gates are singletons + expected = f"<{gate.__class__.__name__} 'ccx' with 2 control qubits, control state = 3 and params=[]>" + self.assertEqual(result, expected) diff --git a/test/python/circuit/test_quantum_circuit_repr.py b/test/python/circuit/test_quantum_circuit_repr.py new file mode 100644 index 000000000000..279ca3d85c32 --- /dev/null +++ b/test/python/circuit/test_quantum_circuit_repr.py @@ -0,0 +1,62 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2024. +# +# 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 https://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. + +"""Test __repr__ method for QuantumCircuit class.""" + +from qiskit.circuit import QuantumCircuit +from test import QiskitTestCase + + +class TestQuantumCircuitRepr(QiskitTestCase): + """Tests for QuantumCircuit.__repr__ method.""" + + def test_quantum_circuit_repr_basic(self): + """Test basic QuantumCircuit repr.""" + qc = QuantumCircuit(2, 2) + result = repr(qc) + expected = f"" + self.assertEqual(result, expected) + + def test_quantum_circuit_repr_with_name(self): + """Test QuantumCircuit repr with custom name.""" + qc = QuantumCircuit(2, 2, name="Bell") + result = repr(qc) + expected = f"" + self.assertEqual(result, expected) + + def test_quantum_circuit_repr_with_instructions(self): + """Test QuantumCircuit repr with instructions.""" + qc = QuantumCircuit(2, 2, name="Bell") + qc.h(0) + qc.cx(0, 1) + qc.measure([0, 1], [0, 1]) + result = repr(qc) + # measure([0, 1], [0, 1]) creates 2 measure instructions + expected = f"" + self.assertEqual(result, expected) + + def test_quantum_circuit_repr_with_global_phase(self): + """Test QuantumCircuit repr with non-zero global phase.""" + qc = QuantumCircuit(1, name="PhaseCircuit", global_phase=1.57) + result = repr(qc) + expected = f"" + self.assertEqual(result, expected) + + def test_quantum_circuit_repr_no_clbits(self): + """Test QuantumCircuit repr without classical bits.""" + qc = QuantumCircuit(3, name="NoClbits") + qc.h(0) + qc.cx(0, 1) + qc.cx(1, 2) + result = repr(qc) + expected = f"" + self.assertEqual(result, expected) diff --git a/test/python/primitives/test_primitives_repr.py b/test/python/primitives/test_primitives_repr.py new file mode 100644 index 000000000000..65ef615d5fb8 --- /dev/null +++ b/test/python/primitives/test_primitives_repr.py @@ -0,0 +1,84 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2024. +# +# 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 https://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. + +"""Test __repr__ methods for StatevectorSampler and StatevectorEstimator classes.""" + +from qiskit.primitives import StatevectorSampler, StatevectorEstimator +from test import QiskitTestCase + + +class TestStatevectorSamplerRepr(QiskitTestCase): + """Tests for StatevectorSampler.__repr__ method.""" + + def test_statevector_sampler_repr_default(self): + """Test StatevectorSampler repr with default parameters.""" + sampler = StatevectorSampler() + result = repr(sampler) + expected = ( + f"" + ) + self.assertEqual(result, expected) + + def test_statevector_sampler_repr_with_shots(self): + """Test StatevectorSampler repr with custom shots.""" + sampler = StatevectorSampler(default_shots=2048) + result = repr(sampler) + expected = "" + self.assertEqual(result, expected) + + def test_statevector_sampler_repr_with_seed(self): + """Test StatevectorSampler repr with seed.""" + sampler = StatevectorSampler(seed=42) + result = repr(sampler) + expected = f"" + self.assertEqual(result, expected) + + def test_statevector_sampler_repr_with_shots_and_seed(self): + """Test StatevectorSampler repr with both shots and seed.""" + sampler = StatevectorSampler(default_shots=2048, seed=42) + result = repr(sampler) + expected = "" + self.assertEqual(result, expected) + + +class TestStatevectorEstimatorRepr(QiskitTestCase): + """Tests for StatevectorEstimator.__repr__ method.""" + + def test_statevector_estimator_repr_default(self): + """Test StatevectorEstimator repr with default parameters.""" + estimator = StatevectorEstimator() + result = repr(estimator) + expected = f"" + self.assertEqual(result, expected) + + def test_statevector_estimator_repr_with_precision(self): + """Test StatevectorEstimator repr with custom precision.""" + estimator = StatevectorEstimator(default_precision=0.01) + result = repr(estimator) + expected = "" + self.assertEqual(result, expected) + + def test_statevector_estimator_repr_with_seed(self): + """Test StatevectorEstimator repr with seed.""" + estimator = StatevectorEstimator(seed=123) + result = repr(estimator) + expected = ( + f"" + ) + self.assertEqual(result, expected) + + def test_statevector_estimator_repr_with_precision_and_seed(self): + """Test StatevectorEstimator repr with both precision and seed.""" + estimator = StatevectorEstimator(default_precision=0.01, seed=123) + result = repr(estimator) + expected = "" + self.assertEqual(result, expected) diff --git a/test/python/transpiler/test_passmanager_repr.py b/test/python/transpiler/test_passmanager_repr.py new file mode 100644 index 000000000000..8bf689c7adad --- /dev/null +++ b/test/python/transpiler/test_passmanager_repr.py @@ -0,0 +1,72 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2024. +# +# 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 https://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. + +"""Test __repr__ method for PassManager class.""" + +from qiskit.transpiler import PassManager +from qiskit.transpiler.passes import Optimize1qGates, Depth +from test import QiskitTestCase + + +class TestPassManagerRepr(QiskitTestCase): + """Tests for PassManager.__repr__ method.""" + + def test_passmanager_repr_empty(self): + """Test empty PassManager repr.""" + pm = PassManager() + result = repr(pm) + expected = f"" + self.assertEqual(result, expected) + + def test_passmanager_repr_single_pass(self): + """Test PassManager repr with single pass.""" + pm = PassManager(Optimize1qGates()) + result = repr(pm) + expected = f"" + self.assertEqual(result, expected) + + def test_passmanager_repr_multiple_passes(self): + """Test PassManager repr with multiple passes.""" + pm = PassManager([Optimize1qGates(), Depth()]) + result = repr(pm) + expected = f"" + self.assertEqual(result, expected) + + def test_passmanager_repr_with_max_iteration(self): + """Test PassManager repr with custom max_iteration.""" + pm = PassManager(Optimize1qGates(), max_iteration=5) + result = repr(pm) + expected = "" + self.assertEqual(result, expected) + + def test_passmanager_repr_with_properties(self): + """Test PassManager repr with properties.""" + pm = PassManager(Optimize1qGates()) + pm.property_set["test_prop1"] = "value1" + pm.property_set["test_prop2"] = "value2" + pm.property_set["test_prop3"] = "value3" + result = repr(pm) + expected = f"" + self.assertEqual(result, expected) + + def test_passmanager_repr_with_many_properties(self): + """Test PassManager repr with more than 3 properties (should truncate).""" + pm = PassManager(Optimize1qGates()) + pm.property_set["prop1"] = "value1" + pm.property_set["prop2"] = "value2" + pm.property_set["prop3"] = "value3" + pm.property_set["prop4"] = "value4" + pm.property_set["prop5"] = "value5" + result = repr(pm) + # Verify full string - should show first 3 properties and "..." + expected = f"" + self.assertEqual(result, expected)