This is an automated email from the ASF dual-hosted git repository.
guanmingchiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/mahout.git
The following commit(s) were added to refs/heads/main by this push:
new d4a1d7e93 MAHOUT-607: add docstring for qumat.py (#609)
d4a1d7e93 is described below
commit d4a1d7e93adeabb149dd0b2286e64f06a14653cb
Author: GUAN-HAO HUANG <[email protected]>
AuthorDate: Thu Nov 13 10:18:04 2025 +0800
MAHOUT-607: add docstring for qumat.py (#609)
---
qumat/qumat.py | 329 +++++++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 284 insertions(+), 45 deletions(-)
diff --git a/qumat/qumat.py b/qumat/qumat.py
index 32c7c3fd6..3bccc8c93 100644
--- a/qumat/qumat.py
+++ b/qumat/qumat.py
@@ -18,7 +18,30 @@ from importlib import import_module
class QuMat:
+ """Unified interface for quantum circuit operations across multiple
backends.
+
+ Provides a consistent API for creating and manipulating quantum circuits
+ using different quantum computing backends (Qiskit, Cirq, Amazon Braket).
+ Abstracts backend-specific details for gate operations, circuit execution,
+ and state measurement.
+
+ :param backend_config: Configuration dictionary for the quantum backend.
+ Must contain ``backend_name`` (str) and ``backend_options`` (dict).
+ The ``backend_options`` should include ``simulator_type`` and
``shots``.
+ :type backend_config: dict
+ """
+
def __init__(self, backend_config):
+ """Create a QuMat instance with the specified backend configuration.
+
+ :param backend_config: Configuration dictionary containing backend name
+ and options. Required keys:
+ - ``backend_name``: Name of the backend (e.g., "qiskit", "cirq",
"amazon_braket")
+ - ``backend_options``: Dictionary with backend-specific options
+ :type backend_config: dict
+ :raises ImportError: If the specified backend module cannot be
imported.
+ :raises KeyError: If required configuration keys are missing.
+ """
self.backend_config = backend_config
self.backend_name = backend_config["backend_name"]
self.backend_module = import_module(
@@ -30,11 +53,25 @@ class QuMat:
self.parameters = {}
def create_empty_circuit(self, num_qubits: int | None = None):
+ """Create an empty quantum circuit with the specified number of qubits.
+
+ Must be called before applying any gates or executing operations.
+
+ :param num_qubits: Number of qubits in the circuit. If ``None``,
+ creates a circuit without pre-allocated qubits.
+ :type num_qubits: int | None, optional
+ """
self.num_qubits = num_qubits
self.circuit = self.backend_module.create_empty_circuit(num_qubits)
def _ensure_circuit_initialized(self):
- """validate that circuit has been created before operations."""
+ """Ensure the circuit has been created before operations.
+
+ Checks if the circuit has been initialized via
``create_empty_circuit()``.
+ Raises ``RuntimeError`` if not initialized.
+
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
if self.circuit is None:
raise RuntimeError(
"circuit not initialized. call
create_empty_circuit(num_qubits) "
@@ -42,14 +79,43 @@ class QuMat:
)
def apply_not_gate(self, qubit_index):
+ """Apply a NOT gate (Pauli-X gate) to the specified qubit.
+
+ Flips the qubit state from |0⟩ to |1⟩ or |1⟩ to |0⟩.
+ Equivalent to the Pauli-X gate.
+
+ :param qubit_index: Index of the qubit.
+ :type qubit_index: int
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
self.backend_module.apply_not_gate(self.circuit, qubit_index)
def apply_hadamard_gate(self, qubit_index):
+ """Apply a Hadamard gate to the specified qubit.
+
+ Creates a superposition state, transforming |0⟩ to (|0⟩ + |1⟩)/√2
+ and |1⟩ to (|0⟩ - |1⟩)/√2.
+
+ :param qubit_index: Index of the qubit.
+ :type qubit_index: int
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
self.backend_module.apply_hadamard_gate(self.circuit, qubit_index)
def apply_cnot_gate(self, control_qubit_index, target_qubit_index):
+ """Apply a Controlled-NOT (CNOT) gate between two qubits.
+
+ Fundamental for entangling qubits. Flips the target qubit if and only
+ if the control qubit is in the |1⟩ state.
+
+ :param control_qubit_index: Index of the control qubit.
+ :type control_qubit_index: int
+ :param target_qubit_index: Index of the target qubit.
+ :type target_qubit_index: int
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
self.backend_module.apply_cnot_gate(
self.circuit, control_qubit_index, target_qubit_index
@@ -58,36 +124,111 @@ class QuMat:
def apply_toffoli_gate(
self, control_qubit_index1, control_qubit_index2, target_qubit_index
):
+ """Apply a Toffoli gate (CCX gate) to three qubits.
+
+ Acts as a quantum AND gate. Flips the target qubit if and only if
+ both control qubits are in the |1⟩ state.
+
+ :param control_qubit_index1: Index of the first control qubit.
+ :type control_qubit_index1: int
+ :param control_qubit_index2: Index of the second control qubit.
+ :type control_qubit_index2: int
+ :param target_qubit_index: Index of the target qubit.
+ :type target_qubit_index: int
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
self.backend_module.apply_toffoli_gate(
self.circuit, control_qubit_index1, control_qubit_index2,
target_qubit_index
)
def apply_swap_gate(self, qubit_index1, qubit_index2):
+ """Swap the states of two qubits.
+
+ :param qubit_index1: Index of the first qubit.
+ :type qubit_index1: int
+ :param qubit_index2: Index of the second qubit.
+ :type qubit_index2: int
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
self.backend_module.apply_swap_gate(self.circuit, qubit_index1,
qubit_index2)
def apply_cswap_gate(
self, control_qubit_index, target_qubit_index1, target_qubit_index2
):
+ """Apply a controlled-SWAP (Fredkin) gate.
+
+ Swaps the states of two target qubits if and only if the control
+ qubit is in the |1⟩ state.
+
+ :param control_qubit_index: Index of the control qubit.
+ :type control_qubit_index: int
+ :param target_qubit_index1: Index of the first target qubit.
+ :type target_qubit_index1: int
+ :param target_qubit_index2: Index of the second target qubit.
+ :type target_qubit_index2: int
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
self.backend_module.apply_cswap_gate(
self.circuit, control_qubit_index, target_qubit_index1,
target_qubit_index2
)
def apply_pauli_x_gate(self, qubit_index):
+ """Apply a Pauli-X gate to the specified qubit.
+
+ Equivalent to the NOT gate. Flips the qubit state from |0⟩ to |1⟩
+ or |1⟩ to |0⟩.
+
+ :param qubit_index: Index of the qubit.
+ :type qubit_index: int
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
self.backend_module.apply_pauli_x_gate(self.circuit, qubit_index)
def apply_pauli_y_gate(self, qubit_index):
+ """Apply a Pauli-Y gate to the specified qubit.
+
+ Rotates the qubit around the Y-axis of the Bloch sphere, affecting
+ both phase and amplitude.
+
+ :param qubit_index: Index of the qubit.
+ :type qubit_index: int
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
self.backend_module.apply_pauli_y_gate(self.circuit, qubit_index)
def apply_pauli_z_gate(self, qubit_index):
+ """Apply a Pauli-Z gate to the specified qubit.
+
+ Rotates the qubit around the Z-axis of the Bloch sphere, altering
+ the phase without changing the amplitude.
+
+ :param qubit_index: Index of the qubit.
+ :type qubit_index: int
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
self.backend_module.apply_pauli_z_gate(self.circuit, qubit_index)
def execute_circuit(self, parameter_values=None):
+ """Execute the quantum circuit and return the measurement results.
+
+ Runs the circuit on the configured backend. For parameterized circuits,
+ provide parameter values to bind before execution.
+
+ :param parameter_values: Dictionary mapping parameter names to
numerical
+ values. Binds these values to circuit parameters before execution.
+ :type parameter_values: dict, optional
+ :returns: Measurement results. Format depends on the backend:
+ - Qiskit/Braket: Dictionary with state strings as keys and counts
as values
+ - Cirq: List of dictionaries with integer states as keys
+ :rtype: dict | list[dict]
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
if self.num_qubits == 0:
shots = self.backend_config["backend_options"].get("shots", 1)
@@ -104,6 +245,17 @@ class QuMat:
)
def bind_parameters(self, parameter_values):
+ """Bind numerical values to circuit parameters.
+
+ Assigns numerical values to symbolic parameters defined in
parameterized
+ gates.
+
+ :param parameter_values: Dictionary mapping parameter names to
numerical
+ values.
+ :type parameter_values: dict
+ :raises ValueError: If a parameter name is not found in the circuit's
+ parameter list.
+ """
for param, value in parameter_values.items():
if param not in self.parameters:
raise ValueError(
@@ -112,52 +264,134 @@ class QuMat:
)
self.parameters[param] = value
- # placeholder method for use in the testing suite
def get_final_state_vector(self):
+ """Return the final state vector of the quantum circuit.
+
+ The complete quantum state vector after circuit execution,
+ representing the full quantum state of all qubits.
+
+ :returns: The final state vector as a numpy array.
+ :rtype: numpy.ndarray
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
return self.backend_module.get_final_state_vector(
self.circuit, self.backend, self.backend_config
)
def draw(self):
+ """Visualize the quantum circuit.
+
+ Generates a visual representation of the circuit. The output format
+ depends on the backend implementation.
+
+ :returns: Circuit visualization. The exact type depends on the backend.
+ :rtype: str | object
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
return self.backend_module.draw_circuit(self.circuit)
def apply_rx_gate(self, qubit_index, angle):
+ """Apply a rotation around the X-axis to the specified qubit.
+
+ Rotates the qubit by the given angle around the X-axis of the Bloch
+ sphere. The angle can be a static value or a parameter name for
+ parameterized circuits.
+
+ :param qubit_index: Index of the qubit.
+ :type qubit_index: int
+ :param angle: Rotation angle in radians. Can be a float or a string
+ parameter name.
+ :type angle: float | str
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
self._handle_parameter(angle)
self.backend_module.apply_rx_gate(self.circuit, qubit_index, angle)
def apply_ry_gate(self, qubit_index, angle):
+ """Apply a rotation around the Y-axis to the specified qubit.
+
+ Rotates the qubit by the given angle around the Y-axis of the Bloch
+ sphere. The angle can be a static value or a parameter name for
+ parameterized circuits.
+
+ :param qubit_index: Index of the qubit.
+ :type qubit_index: int
+ :param angle: Rotation angle in radians. Can be a float or a string
+ parameter name.
+ :type angle: float | str
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
self._handle_parameter(angle)
self.backend_module.apply_ry_gate(self.circuit, qubit_index, angle)
def apply_rz_gate(self, qubit_index, angle):
+ """Apply a rotation around the Z-axis to the specified qubit.
+
+ Rotates the qubit by the given angle around the Z-axis of the Bloch
+ sphere. The angle can be a static value or a parameter name for
+ parameterized circuits.
+
+ :param qubit_index: Index of the qubit.
+ :type qubit_index: int
+ :param angle: Rotation angle in radians. Can be a float or a string
+ parameter name.
+ :type angle: float | str
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
self._handle_parameter(angle)
self.backend_module.apply_rz_gate(self.circuit, qubit_index, angle)
def _handle_parameter(self, param_name):
+ """Register parameter names when parameterized gates are applied.
+
+ Automatically adds string parameter names to the parameters dictionary
+ if not already registered.
+
+ :param param_name: Parameter name to handle. If it's a string,
+ registers it as a parameter.
+ :type param_name: str | float
+ """
if isinstance(param_name, str) and param_name not in self.parameters:
self.parameters[param_name] = None
def apply_u_gate(self, qubit_index, theta, phi, lambd):
+ """Apply a U gate (universal single-qubit gate) to the specified qubit.
+
+ A universal single-qubit gate parameterized by three angles (theta,
+ phi, lambd) that can represent any single-qubit unitary operation.
+
+ :param qubit_index: Index of the qubit.
+ :type qubit_index: int
+ :param theta: First rotation angle in radians.
+ :type theta: float
+ :param phi: Second rotation angle in radians.
+ :type phi: float
+ :param lambd: Third rotation angle in radians.
+ :type lambd: float
+ :raises RuntimeError: If the circuit has not been initialized.
+ """
self._ensure_circuit_initialized()
self.backend_module.apply_u_gate(self.circuit, qubit_index, theta,
phi, lambd)
def swap_test(self, ancilla_qubit, qubit1, qubit2):
- """
- Implements the swap test circuit for measuring overlap between two
quantum states.
-
- The swap test measures the inner product between the states on qubit1
and qubit2.
- The probability of measuring the ancilla qubit in state |0> is related
to the overlap
- as: P(0) = (1 + |<ψ|φ>|²) / 2
-
- Args:
- ancilla_qubit: Index of the ancilla qubit (should be initialized
to |0>)
- qubit1: Index of the first qubit containing state |ψ>
- qubit2: Index of the second qubit containing state |φ>
+ """Implement the swap test circuit for measuring overlap between two
quantum states.
+
+ Measures the inner product between the states on ``qubit1`` and
``qubit2``.
+ The probability of measuring the ancilla qubit in state |0⟩ is related
+ to the overlap as: P(0) = (1 + |⟨ψ|φ⟩|²) / 2
+
+ :param ancilla_qubit: Index of the ancilla qubit (should be
initialized to |0⟩).
+ :type ancilla_qubit: int
+ :param qubit1: Index of the first qubit containing state |ψ⟩.
+ :type qubit1: int
+ :param qubit2: Index of the second qubit containing state |φ⟩.
+ :type qubit2: int
+ :raises RuntimeError: If the circuit has not been initialized.
"""
# Apply Hadamard to ancilla qubit
self.apply_hadamard_gate(ancilla_qubit)
@@ -169,27 +403,31 @@ class QuMat:
self.apply_hadamard_gate(ancilla_qubit)
def measure_overlap(self, qubit1, qubit2, ancilla_qubit=0):
- """
- Measures the overlap (fidelity) between two quantum states using the
swap test.
-
- This method creates a swap test circuit to calculate the similarity
between
- the quantum states on qubit1 and qubit2. It returns the squared
overlap |<ψ|φ>|²,
- which represents the fidelity between the two states.
-
- The swap test measures P(ancilla=0), which is related to overlap as:
- P(0) = (1 + |<ψ|φ>|²) / 2
-
- However, for certain states (especially identical excited states),
global phase
- effects may cause the ancilla to measure predominantly |1> instead of
|0>.
- This method handles both cases by taking the measurement probability
closer to 1.
-
- Args:
- qubit1: Index of the first qubit containing state |ψ>
- qubit2: Index of the second qubit containing state |φ>
- ancilla_qubit: Index of the ancilla qubit (default: 0, should be
initialized to |0>)
-
- Returns:
- float: The squared overlap |<ψ|φ>|² between the two states
(fidelity)
+ """Measure the overlap (fidelity) between two quantum states using the
swap test.
+
+ Creates a swap test circuit to calculate the similarity between the
+ quantum states on ``qubit1`` and ``qubit2``. Returns the squared
overlap
+ |⟨ψ|φ⟩|², which represents the fidelity between the two states.
+
+ The swap test measures P(ancilla=0), related to overlap as:
+ P(0) = (1 + |⟨ψ|φ⟩|²) / 2
+
+ For certain states (especially identical excited states), global phase
+ effects may cause the ancilla to measure predominantly |1⟩ instead of
|0⟩.
+ This method handles both cases by taking the measurement probability
+ closer to 1.
+
+ :param qubit1: Index of the first qubit containing state |ψ⟩.
+ :type qubit1: int
+ :param qubit2: Index of the second qubit containing state |φ⟩.
+ :type qubit2: int
+ :param ancilla_qubit: Index of the ancilla qubit. Default is 0. Should
be
+ initialized to |0⟩.
+ :type ancilla_qubit: int, optional
+ :returns: The squared overlap |⟨ψ|φ⟩|² between the two states
(fidelity),
+ clamped to the range [0.0, 1.0].
+ :rtype: float
+ :raises RuntimeError: If the circuit has not been initialized.
"""
# Perform the swap test
self.swap_test(ancilla_qubit, qubit1, qubit2)
@@ -204,17 +442,18 @@ class QuMat:
return overlap_squared
def calculate_prob_zero(self, results, ancilla_qubit):
- """
- Calculate the probability of measuring the ancilla qubit in |0> state.
-
- Delegates to backend-specific implementation via the backend module.
-
- Args:
- results: Measurement results from execute_circuit()
- ancilla_qubit: Index of the ancilla qubit
-
- Returns:
- float: Probability of measuring ancilla in |0> state
+ """Calculate the probability of measuring the ancilla qubit in |0⟩
state.
+
+ Delegates to the backend-specific implementation. Different backends
+ may use different qubit ordering conventions (little-endian vs
big-endian).
+
+ :param results: Measurement results from ``execute_circuit()``. Format
+ depends on the backend.
+ :type results: dict | list[dict]
+ :param ancilla_qubit: Index of the ancilla qubit.
+ :type ancilla_qubit: int
+ :returns: Probability of measuring the ancilla qubit in |0⟩ state.
+ :rtype: float
"""
return self.backend_module.calculate_prob_zero(
results, ancilla_qubit, self.num_qubits