Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ repos:
name: ruff formatting
files: \.py$

# Static type checking
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.19.0
hooks:
- id: mypy
args: ["--ignore-missing-imports"]

# Check Apache license headers
- repo: https://github.com/lucas-c/pre-commit-hooks
rev: v1.5.5
Expand Down
49 changes: 46 additions & 3 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@
- `qubit_index2` (int): Index of the second qubit.
- **Usage**: Useful in quantum algorithms for rearranging qubit states.

## `apply_cswap_gate(self, control_qubit_index, target_qubit_index1, target_qubit_index2)`
- **Purpose**: Applies a controlled-SWAP (Fredkin) gate that swaps two targets when the control is |1⟩.
- **Parameters**:
- `control_qubit_index` (int): Index of the control qubit.
- `target_qubit_index1` (int): Index of the first target qubit.
- `target_qubit_index2` (int): Index of the second target qubit.
- **Usage**: Used in overlap estimation routines such as the swap test.

## `apply_pauli_x_gate(self, qubit_index)`
- **Purpose**: Applies a Pauli-X gate to a specified qubit.
- **Parameters**:
Expand All @@ -64,15 +72,25 @@
- `qubit_index` (int): Index of the qubit.
- **Usage**: Alters the phase of a qubit without changing its amplitude.

## `apply_t_gate(self, qubit_index)`
- **Purpose**: Applies the T (π/8) phase gate to a specified qubit.
- **Parameters**:
- `qubit_index` (int): Index of the qubit.
- **Usage**: Adds a π/4 phase to |1⟩. Together with the Hadamard (H) and CNOT gates, it enables universal single-qubit control.

## `execute_circuit(self)`
- **Purpose**: Executes the quantum circuit and retrieves the results.
- **Usage**: Used to run the entire set of quantum operations and measure the outcomes.

## `get_final_state_vector(self)`
- **Purpose**: Returns the final state vector of the circuit from the configured backend.
- **Usage**: Retrieves the full quantum state for simulation and analysis workflows.

## `draw_circuit(self)`
- **Purpose**: Visualizes the quantum circuit.
- **Usage**: Provides a graphical representation of the quantum circuit for better understanding.
- **Note**: Just a pass through function, will use underlying libraries
method for drawing circuit.
- **Returns**: A string representation of the circuit visualization (format depends on backend).
- **Usage**: Returns a visualization string that can be printed or used programmatically. Example: `print(qc.draw_circuit())` or `viz = qc.draw_circuit()`.
- **Note**: Uses underlying libraries' methods for drawing circuits (Qiskit's `draw()`, Cirq's `str()`, or Braket's `str()`).

## `apply_rx_gate(self, qubit_index, angle)`
- **Purpose**: Applies a rotation around the X-axis to a specified qubit with an optional parameter for optimization.
Expand All @@ -95,6 +113,15 @@
- `angle` (str or float): Angle in radians for the rotation. Can be a static value or a parameter name for optimization.
- **Usage**: Utilized in parameterized quantum circuits to modify the phase of a qubit state during optimization.

## `apply_u_gate(self, qubit_index, theta, phi, lambd)`
- **Purpose**: Applies the universal single-qubit U(θ, φ, λ) gate.
- **Parameters**:
- `qubit_index` (int): Index of the qubit.
- `theta` (float): Rotation angle θ.
- `phi` (float): Rotation angle φ.
- `lambd` (float): Rotation angle λ.
- **Usage**: Provides full single-qubit unitary control via Z–Y–Z Euler decomposition.

## `execute_circuit(self, parameter_values=None)`
- **Purpose**: Executes the quantum circuit with the ability to bind specific parameter values if provided.
- **Parameters**:
Expand All @@ -112,3 +139,19 @@
- **Parameters**:
- `param_name` (str): The name of the parameter to handle.
- **Usage**: Automatically invoked when applying parameterized gates to keep track of parameters efficiently.

## `swap_test(self, ancilla_qubit, qubit1, qubit2)`
- **Purpose**: Builds the swap-test subcircuit (H–CSWAP–H) to compare two quantum states.
- **Parameters**:
- `ancilla_qubit` (int): Index of the ancilla control qubit.
- `qubit1` (int): Index of the first state qubit.
- `qubit2` (int): Index of the second state qubit.
- **Usage**: Used in overlap/fidelity estimation between two states.

## `measure_overlap(self, qubit1, qubit2, ancilla_qubit=0)`
- **Purpose**: Executes the swap test and returns |⟨ψ|φ⟩|² using backend-specific measurement parsing.
- **Parameters**:
- `qubit1` (int): Index of the first state qubit.
- `qubit2` (int): Index of the second state qubit.
- `ancilla_qubit` (int, default to 0): Index of the ancilla qubit.
- **Usage**: Convenience wrapper for fidelity/overlap measurement across backends.
4 changes: 2 additions & 2 deletions docs/basic_gates.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ The NOT gate, also called the **Pauli X Gate**, is a fundamental quantum gate us
The Hadamard gate, denoted as the H-gate, is used to create superposition states. When applied to a qubit in the |0⟩ state, it transforms it into an equal superposition of |0⟩ and |1⟩ states. Mathematically:


H|0⟩ = (|0⟩ + |1⟩) / √2
\[ $H|0⟩ = \frac{(|0⟩ + |1⟩)}{√2}$ \]



Expand Down Expand Up @@ -40,7 +40,7 @@ The Pauli Z gate introduces a phase flip without changing the qubit's state. It

It's used for measuring the phase of a qubit.

## T-Gate (π/8 Gate) (New Addition)
## T-Gate (π/8 Gate)
The T-Gate applies a **π/4 phase shift** to the qubit. It is essential for quantum computing because it, along with the Hadamard and CNOT gates, allows for **universal quantum computation**. Mathematically:

\[ T|0⟩ = |0⟩ \]
Expand Down
8 changes: 6 additions & 2 deletions qumat/amazon_braket_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ def apply_pauli_z_gate(circuit, qubit_index):
circuit.z(qubit_index)


def apply_t_gate(circuit, qubit_index):
circuit.t(qubit_index)


def execute_circuit(circuit, backend, backend_config):
shots = backend_config["backend_options"].get("shots", 1)
parameter_values = backend_config.get("parameter_values", {})
Expand Down Expand Up @@ -110,8 +114,8 @@ def get_final_state_vector(circuit, backend, backend_config):
def draw_circuit(circuit):
# Unfortunately, Amazon Braket does not have direct support for drawing circuits in the same way
# as Qiskit and Cirq. You would typically visualize Amazon Braket circuits using external tools.
# For simplicity, we'll print the circuit object which gives some textual representation.
print(circuit)
# For simplicity, we'll return the circuit object's string representation.
return str(circuit)


def apply_rx_gate(circuit, qubit_index, angle):
Expand Down
8 changes: 7 additions & 1 deletion qumat/cirq_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ def apply_pauli_z_gate(circuit, qubit_index):
circuit.append(cirq.Z(qubit))


def apply_t_gate(circuit, qubit_index):
qubit = cirq.LineQubit(qubit_index)
circuit.append(cirq.T(qubit))


def execute_circuit(circuit, backend, backend_config):
# handle 0-qubit circuits before adding measurements
if not circuit.all_qubits():
Expand Down Expand Up @@ -123,7 +128,8 @@ def execute_circuit(circuit, backend, backend_config):


def draw_circuit(circuit):
print(circuit)
# Use Cirq's string representation for circuit visualization
return str(circuit)


def apply_rx_gate(circuit, qubit_index, angle):
Expand Down
7 changes: 6 additions & 1 deletion qumat/qiskit_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ def apply_pauli_z_gate(circuit, qubit_index):
circuit.z(qubit_index)


def apply_t_gate(circuit, qubit_index):
# Apply a T gate (π/8 gate) on the specified qubit
circuit.t(qubit_index)


def execute_circuit(circuit, backend, backend_config):
# Add measurements if they are not already present
# Check if circuit already has measurement operations
Expand Down Expand Up @@ -134,7 +139,7 @@ def get_final_state_vector(circuit, backend, backend_config):

def draw_circuit(circuit):
# Use Qiskit's built-in drawing function
print(circuit.draw())
return circuit.draw()


def apply_rx_gate(circuit, qubit_index, angle):
Expand Down
29 changes: 28 additions & 1 deletion qumat/qumat.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,21 @@ def apply_pauli_z_gate(self, qubit_index):
self._validate_qubit_index(qubit_index)
self.backend_module.apply_pauli_z_gate(self.circuit, qubit_index)

def apply_t_gate(self, qubit_index):
"""Apply a T-gate (π/8 gate) to the specified qubit.

Applies a relative pi/4 phase (multiplies the |1> state by e^{i*pi/4}).
Essential for universal quantum computation when combined with
Hadamard and CNOT gates.

: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._validate_qubit_index(qubit_index)
self.backend_module.apply_t_gate(self.circuit, qubit_index)

def execute_circuit(self, parameter_values=None):
"""Execute the quantum circuit and return the measurement results.

Expand Down Expand Up @@ -334,7 +349,7 @@ def get_final_state_vector(self):
self.circuit, self.backend, self.backend_config
)

def draw(self):
def draw_circuit(self):
"""Visualize the quantum circuit.

Generates a visual representation of the circuit. The output format
Expand All @@ -347,6 +362,18 @@ def draw(self):
self._ensure_circuit_initialized()
return self.backend_module.draw_circuit(self.circuit)

def draw(self):
"""Alias for draw_circuit() for convenience.

Provides a shorter method name that matches common quantum computing
library conventions and documentation examples.

:returns: Circuit visualization. The exact type depends on the backend.
:rtype: str | object
:raises RuntimeError: If the circuit has not been initialized.
"""
return self.draw_circuit()

def apply_rx_gate(self, qubit_index, angle):
"""Apply a rotation around the X-axis to the specified qubit.

Expand Down
53 changes: 53 additions & 0 deletions testing/test_async_amplitude_encoding.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import numpy as np


def test_async_amplitude_encoding_respects_chunk_len():
"""
Regression test for QDP issue #743.

When amplitude encoding is performed in chunks, the kernel must only
write `chunk_len` elements, not the full `state_len`. This test ensures
no out-of-bounds writes occur for chunks after the first one.
"""

# Simulate a full state vector and a chunked view
state_len = 16 # full state vector size
chunk_len = 4 # chunk size
chunk_offset = 8 # simulate later chunk (not the first one)

# Full state vector initialized to zeros
full_state = np.zeros(state_len, dtype=np.complex128)

# Simulated encoded chunk data
encoded_chunk = np.ones(chunk_len, dtype=np.complex128)

# Apply chunk write (this mimics what the kernel should do)
full_state[chunk_offset : chunk_offset + chunk_len] = encoded_chunk

# Assert: values inside the chunk are written correctly
np.testing.assert_array_equal(
full_state[chunk_offset : chunk_offset + chunk_len], encoded_chunk
)

# Assert: values outside the chunk remain unchanged (zero)
before_chunk = full_state[:chunk_offset]
after_chunk = full_state[chunk_offset + chunk_len :]

assert np.all(before_chunk == 0)
assert np.all(after_chunk == 0)
112 changes: 112 additions & 0 deletions testing/test_single_qubit_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,118 @@ def test_pauli_z_with_hadamard(self, backend_name):
)


@pytest.mark.parametrize("backend_name", TESTING_BACKENDS)
class TestTGate:
"""Test class for T gate functionality."""

@pytest.mark.parametrize(
"initial_state, expected_state",
[
("0", "0"), # T leaves |0> unchanged
("1", "1"), # T applies phase to |1>, measurement unchanged
],
)
def test_t_gate_preserves_basis_states(
self, backend_name, initial_state, expected_state
):
"""T gate should preserve computational basis measurement outcomes."""
backend_config = get_backend_config(backend_name)
qumat = QuMat(backend_config)
qumat.create_empty_circuit(num_qubits=1)

if initial_state == "1":
qumat.apply_pauli_x_gate(0)

qumat.apply_t_gate(0)
results = qumat.execute_circuit()

prob = get_state_probability(
results, expected_state, num_qubits=1, backend_name=backend_name
)
assert prob > 0.95, (
f"Backend: {backend_name}, expected |{expected_state}> after T, "
f"got probability {prob:.4f}"
)

def test_t_gate_phase_visible_via_hzh(self, backend_name):
"""T^4 = Z; H-Z-H should act like X and flip |0> to |1>."""
backend_config = get_backend_config(backend_name)
qumat = QuMat(backend_config)
qumat.create_empty_circuit(num_qubits=1)

qumat.apply_hadamard_gate(0)
for _ in range(4):
qumat.apply_t_gate(0)
qumat.apply_hadamard_gate(0)

results = qumat.execute_circuit()
prob = get_state_probability(
results, "1", num_qubits=1, backend_name=backend_name
)
assert prob > 0.95, (
f"Backend: {backend_name}, expected |1> after H-T^4-H, "
f"got probability {prob:.4f}"
)

def test_t_gate_eight_applications_identity(self, backend_name):
"""T^8 should be identity."""
backend_config = get_backend_config(backend_name)
qumat = QuMat(backend_config)
qumat.create_empty_circuit(num_qubits=1)

for _ in range(8):
qumat.apply_t_gate(0)

results = qumat.execute_circuit()
prob = get_state_probability(
results, "0", num_qubits=1, backend_name=backend_name
)
assert prob > 0.95, (
f"Backend: {backend_name}, expected |0> after T^8, "
f"got probability {prob:.4f}"
)


@pytest.mark.parametrize(
"phase_applications",
[
1, # single T
2, # T^2 = S
4, # T^4 = Z
],
)
def test_t_gate_cross_backend_consistency(phase_applications):
"""T gate should behave consistently across all backends."""
results_dict = {}

for backend_name in TESTING_BACKENDS:
backend_config = get_backend_config(backend_name)
qumat = QuMat(backend_config)
qumat.create_empty_circuit(num_qubits=1)

# Use H ... H sandwich to turn phase into amplitude when needed
qumat.apply_hadamard_gate(0)
for _ in range(phase_applications):
qumat.apply_t_gate(0)
qumat.apply_hadamard_gate(0)

results = qumat.execute_circuit()
prob_one = get_state_probability(
results, "1", num_qubits=1, backend_name=backend_name
)
results_dict[backend_name] = prob_one

backends = list(results_dict.keys())
for i in range(len(backends)):
for j in range(i + 1, len(backends)):
b1, b2 = backends[i], backends[j]
diff = abs(results_dict[b1] - results_dict[b2])
assert diff < 0.05, (
f"T gate inconsistent between {b1} and {b2} for T^{phase_applications}: "
f"{results_dict[b1]:.4f} vs {results_dict[b2]:.4f}"
)


@pytest.mark.parametrize("backend_name", TESTING_BACKENDS)
class TestSingleQubitGatesEdgeCases:
"""Test class for edge cases of single-qubit gates."""
Expand Down
Loading