Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Types of changes:
## Unreleased

### Added
- Added support for the `c3x` (3-controlled X) and `rc3x`/`rcccx` (relative-phase 3-controlled X) gates, decomposed into basis gates following qiskit's `C3XGate`/`RC3XGate` definitions. Also extended the `ctrl @` modifier chain so that 3- and 4-control stacks on `x` (e.g. `ctrl @ ctrl @ ctrl @ x`, `ctrl(4) @ x`) resolve to `c3x`/`c4x`. ([#320](https://github.com/qBraid/pyqasm/pull/320))

### Improved / Modified

Expand All @@ -23,6 +24,7 @@ Types of changes:
### Removed

### Fixed
- Fixed the `c4x` (4-controlled X) gate, which previously raised `TypeError: c4x_gate() takes 4 positional arguments but 5 were given` because it was declared with four parameters for a five-qubit gate. It is now implemented via qiskit's structured `rc3x`/`c3sx`/`cphaseshift` decomposition. ([#320](https://github.com/qBraid/pyqasm/pull/320))
- Fixed classical register declarations not being visible inside `box` scope, causing "Missing clbit register declaration" errors for measurement statements inside box blocks. ([#306](https://github.com/qBraid/pyqasm/pull/306))
- Fixed the backend-dependent `dt` duration unit being incorrectly relabeled as `ns` when unrolling `delay` and `box` statements without a `device_cycle_time`. Since `dt` cannot be converted to SI units without a sample rate, it is now preserved as `dt`. ([#317](https://github.com/qBraid/pyqasm/pull/317))

Expand Down
91 changes: 90 additions & 1 deletion docs/gate_decompositions.md
Original file line number Diff line number Diff line change
Expand Up @@ -616,4 +616,93 @@ q_3: ┤ H ├─■────────┤ H ├┤ H ├─■────
« │U1(π/8) ├───┤┌───┐ │U1(-π/8) ├───┤┌───┐ │U1(π/8) ┌───┐
«q_3:─■────────┤ H ├┤ H ├─■─────────┤ H ├┤ H ├─■────────┤ H ├
« └───┘└───┘ └───┘└───┘ └───┘
```
```
## [C3X Gate](../src/pyqasm/maps/gates.py#L766)

The C3X (3-Controlled-X) gate is implemented using the following qiskit decomposition
(translated from `qiskit.circuit.library.C3XGate.definition`, built from `h`, `p` and `cx`):

```python
In [1]: from qiskit.circuit.library import C3XGate
In [2]: from qiskit import QuantumCircuit
In [3]: qc = QuantumCircuit(4); qc.append(C3XGate(), range(4))
In [4]: qc.draw()
Out[4]:

q_0: ──■──
q_1: ──■──
q_2: ──■──
┌─┴─┐
q_3: ┤ X ├
└───┘

In [5]: qc.decompose().draw()
Out[5]:
...
┌────────┐ ...
q_0: ┤ P(π/8) ├──■───────────────■─────────────...
├────────┤┌─┴─┐┌─────────┐┌─┴─┐ ...
q_1: ┤ P(π/8) ├┤ X ├┤ P(-π/8) ├┤ X ├──■───────────────────...
├────────┤└───┘└─────────┘└───┘┌─┴─┐┌─────────┐┌─┴─┐ ...
q_2: ┤ P(π/8) ├─────────────────────┤ X ├┤ P(-π/8) ├┤ X ├──...
├───┬────┤┌────────┐ └───┘└─────────┘└───┘ ...
q_3: ┤ H ├────┤ P(π/8) ├ ... (h · p(±π/8) · cx ladder) ... ┤ H ├
└───┘ └────────┘ └───┘
```

The verified decomposition reproduces the exact `C3XGate` unitary (no global phase).

## [RC3X Gate](../src/pyqasm/maps/gates.py#L809)

The RC3X (relative-phase 3-Controlled-X, a.k.a. `rcccx`) gate is a phase-relaxed variant of
`c3x` that uses fewer gates. It is implemented using the following qiskit decomposition
(translated from `qiskit.circuit.library.RC3XGate.definition`, built from `h`, `t`, `tdg`, `cx`):

```python
In [1]: from qiskit.circuit.library import RC3XGate
In [2]: from qiskit import QuantumCircuit
In [3]: qc = QuantumCircuit(4); qc.append(RC3XGate(), range(4))
In [4]: qc.decompose().draw()
Out[4]:

q_0: ──────────────────────────────■─────────────────────■────────────────────────
│ │
q_1: ───────────────────────────────────────■────────────────────■─────────────────
│ │ │ │
q_2: ────────────■──────────────────┼────────┼───────────┼─────────┼───────■─────────
┌───┐┌───┐┌─┴─┐┌─────┐┌───┐┌─┴─┐┌───┐┌─┴─┐┌─────┐┌─┴─┐┌───┐┌─┴─┐┌─────┐┌─┴─┐┌───┐
q_3: ┤ H ├┤ T ├┤ X ├┤ Tdg ├┤ H ├┤ X ├┤ T ├┤ X ├┤ Tdg ├┤ X ├┤ T ├┤ X ├┤ Tdg ├┤ X ├┤ H ├
└───┘└───┘└───┘└─────┘└───┘└───┘└───┘└───┘└─────┘└───┘└───┘└───┘└─────┘└───┘└───┘
```

The verified decomposition reproduces the exact `RC3XGate` unitary (no global phase).

## [C4X Gate](../src/pyqasm/maps/gates.py#L867)

The C4X (4-Controlled-X) gate is implemented using qiskit's structured decomposition
(translated from `qiskit.circuit.library.C4XGate.definition`) in terms of `h`,
`cphaseshift` (`cp`), the relative-phase `rc3x` and its inverse, and `c3sx`:

```python
In [1]: from qiskit.circuit.library import C4XGate
In [2]: from qiskit import QuantumCircuit
In [3]: qc = QuantumCircuit(5); qc.append(C4XGate(), range(5))
In [4]: qc.decompose().draw()
Out[4]:
┌────────┐ ┌───────────┐
q_0: ──────────────┤0 ├───────────────┤0 ├──■───
│ │ │ │ │
q_1: ──────────────┤1 ├───────────────┤1 ├──■───
│ rcccx │ │ rcccx_dg │ │
q_2: ──────────────┤2 ├───────────────┤2 ├──■───
│ │ │ │ │
q_3: ──────■───────┤3 ├──────■────────┤3 ├──┼───
┌───┐ │P(π/2) └────────┘┌───┐ │P(-π/2) └───────────┘┌─┴──┐
q_4: ┤ H ├─■─────────┤ H ├───┤ H ├─■───────────┤ H ├─────┤ Sx ├
└───┘ └───┘ └───┘ └───┘ └────┘
```

The decomposition reproduces the `C4XGate` unitary up to a global phase, inherited from
pyqasm's `c3sx` implementation; this is physically irrelevant for the standalone gate.
140 changes: 128 additions & 12 deletions src/pyqasm/maps/gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,23 +763,127 @@ def c3sx_gate(
return result


def c3x_gate(
qubit0: IndexedIdentifier,
qubit1: IndexedIdentifier,
qubit2: IndexedIdentifier,
qubit3: IndexedIdentifier,
) -> list[QuantumGate]:
"""Implements the c3x (3-controlled X) gate as a decomposition of h/p/cx."""
pi = CONSTANTS_MAP["pi"]
result: list[QuantumGate] = []
result.extend(one_qubit_gate_op("h", qubit3))
result.extend(one_qubit_rotation_op("p", pi / 8, qubit0))
result.extend(one_qubit_rotation_op("p", pi / 8, qubit1))
result.extend(one_qubit_rotation_op("p", pi / 8, qubit2))
result.extend(one_qubit_rotation_op("p", pi / 8, qubit3))
result.extend(two_qubit_gate_op("cx", qubit0, qubit1))
result.extend(one_qubit_rotation_op("p", -pi / 8, qubit1))
result.extend(two_qubit_gate_op("cx", qubit0, qubit1))
result.extend(two_qubit_gate_op("cx", qubit1, qubit2))
result.extend(one_qubit_rotation_op("p", -pi / 8, qubit2))
result.extend(two_qubit_gate_op("cx", qubit0, qubit2))
result.extend(one_qubit_rotation_op("p", pi / 8, qubit2))
result.extend(two_qubit_gate_op("cx", qubit1, qubit2))
result.extend(one_qubit_rotation_op("p", -pi / 8, qubit2))
result.extend(two_qubit_gate_op("cx", qubit0, qubit2))
result.extend(two_qubit_gate_op("cx", qubit2, qubit3))
result.extend(one_qubit_rotation_op("p", -pi / 8, qubit3))
result.extend(two_qubit_gate_op("cx", qubit1, qubit3))
result.extend(one_qubit_rotation_op("p", pi / 8, qubit3))
result.extend(two_qubit_gate_op("cx", qubit2, qubit3))
result.extend(one_qubit_rotation_op("p", -pi / 8, qubit3))
result.extend(two_qubit_gate_op("cx", qubit0, qubit3))
result.extend(one_qubit_rotation_op("p", pi / 8, qubit3))
result.extend(two_qubit_gate_op("cx", qubit2, qubit3))
result.extend(one_qubit_rotation_op("p", -pi / 8, qubit3))
result.extend(two_qubit_gate_op("cx", qubit1, qubit3))
result.extend(one_qubit_rotation_op("p", pi / 8, qubit3))
result.extend(two_qubit_gate_op("cx", qubit2, qubit3))
result.extend(one_qubit_rotation_op("p", -pi / 8, qubit3))
result.extend(two_qubit_gate_op("cx", qubit0, qubit3))
result.extend(one_qubit_gate_op("h", qubit3))
return result


def rc3x_gate(
qubit0: IndexedIdentifier,
qubit1: IndexedIdentifier,
qubit2: IndexedIdentifier,
qubit3: IndexedIdentifier,
) -> list[QuantumGate]:
"""Implements the rc3x (relative-phase 3-controlled X) gate."""
result: list[QuantumGate] = []
result.extend(one_qubit_gate_op("h", qubit3))
result.extend(one_qubit_gate_op("t", qubit3))
result.extend(two_qubit_gate_op("cx", qubit2, qubit3))
result.extend(one_qubit_gate_op("tdg", qubit3))
result.extend(one_qubit_gate_op("h", qubit3))
result.extend(two_qubit_gate_op("cx", qubit0, qubit3))
result.extend(one_qubit_gate_op("t", qubit3))
result.extend(two_qubit_gate_op("cx", qubit1, qubit3))
result.extend(one_qubit_gate_op("tdg", qubit3))
result.extend(two_qubit_gate_op("cx", qubit0, qubit3))
result.extend(one_qubit_gate_op("t", qubit3))
result.extend(two_qubit_gate_op("cx", qubit1, qubit3))
result.extend(one_qubit_gate_op("tdg", qubit3))
result.extend(one_qubit_gate_op("h", qubit3))
result.extend(one_qubit_gate_op("t", qubit3))
result.extend(two_qubit_gate_op("cx", qubit2, qubit3))
result.extend(one_qubit_gate_op("tdg", qubit3))
result.extend(one_qubit_gate_op("h", qubit3))
return result


def _rc3x_dg_gate(
qubit0: IndexedIdentifier,
qubit1: IndexedIdentifier,
qubit2: IndexedIdentifier,
qubit3: IndexedIdentifier,
) -> list[QuantumGate]:
"""Inverse of :func:`rc3x_gate`, used in the c4x decomposition."""
result: list[QuantumGate] = []
result.extend(one_qubit_gate_op("h", qubit3))
result.extend(one_qubit_gate_op("t", qubit3))
result.extend(two_qubit_gate_op("cx", qubit2, qubit3))
result.extend(one_qubit_gate_op("tdg", qubit3))
result.extend(one_qubit_gate_op("h", qubit3))
result.extend(one_qubit_gate_op("t", qubit3))
result.extend(two_qubit_gate_op("cx", qubit1, qubit3))
result.extend(one_qubit_gate_op("tdg", qubit3))
result.extend(two_qubit_gate_op("cx", qubit0, qubit3))
result.extend(one_qubit_gate_op("t", qubit3))
result.extend(two_qubit_gate_op("cx", qubit1, qubit3))
result.extend(one_qubit_gate_op("tdg", qubit3))
result.extend(two_qubit_gate_op("cx", qubit0, qubit3))
result.extend(one_qubit_gate_op("h", qubit3))
result.extend(one_qubit_gate_op("t", qubit3))
result.extend(two_qubit_gate_op("cx", qubit2, qubit3))
result.extend(one_qubit_gate_op("tdg", qubit3))
result.extend(one_qubit_gate_op("h", qubit3))
return result


def c4x_gate(
qubit0: IndexedIdentifier,
qubit1: IndexedIdentifier,
qubit2: IndexedIdentifier,
qubit3: IndexedIdentifier,
qubit4: IndexedIdentifier,
) -> list[QuantumGate]:
"""
Implements the c4x gate
"""
return [
QuantumGate(
modifiers=[],
name=Identifier(name="c4x"),
arguments=[],
qubits=[qubit0, qubit1, qubit2, qubit3],
)
]
"""Implements the c4x (4-controlled X) gate via rc3x, c3sx and cphaseshift."""
pi = CONSTANTS_MAP["pi"]
result: list[QuantumGate] = []
result.extend(one_qubit_gate_op("h", qubit4))
result.extend(cphaseshift_gate(pi / 2, qubit3, qubit4))
result.extend(one_qubit_gate_op("h", qubit4))
result.extend(rc3x_gate(qubit0, qubit1, qubit2, qubit3))
result.extend(one_qubit_gate_op("h", qubit4))
result.extend(cphaseshift_gate(-pi / 2, qubit3, qubit4))
result.extend(one_qubit_gate_op("h", qubit4))
result.extend(_rc3x_dg_gate(qubit0, qubit1, qubit2, qubit3))
result.extend(c3sx_gate(qubit0, qubit1, qubit2, qubit4))
return result


def prx_gate(theta, phi, qubit_id) -> list[QuantumGate]:
Expand Down Expand Up @@ -918,7 +1022,13 @@ def two_qubit_gate_op(
"rccx": rccx_gate,
}

FOUR_QUBIT_OP_MAP = {"c3sx": c3sx_gate, "c3sqrtx": c3sx_gate}
FOUR_QUBIT_OP_MAP = {
"c3sx": c3sx_gate,
"c3sqrtx": c3sx_gate,
"c3x": c3x_gate,
"rc3x": rc3x_gate,
"rcccx": rc3x_gate,
}

FIVE_QUBIT_OP_MAP = {
"c4x": c4x_gate,
Expand Down Expand Up @@ -1116,6 +1226,8 @@ def map_qasm_inv_op_to_callable(op_name: str):
"u": "cu",
"swap": "cswap",
"cx": "ccx",
"ccx": "c3x",
"c3x": "c4x",
}


Expand All @@ -1142,6 +1254,10 @@ def map_qasm_ctrl_op_to_callable(op_name: str, ctrl_count: int):
return TWO_QUBIT_OP_MAP[ctrl_op_name], 2
if ctrl_op_name in THREE_QUBIT_OP_MAP:
return THREE_QUBIT_OP_MAP[ctrl_op_name], 3
if ctrl_op_name in FOUR_QUBIT_OP_MAP:
return FOUR_QUBIT_OP_MAP[ctrl_op_name], 4
if ctrl_op_name in FIVE_QUBIT_OP_MAP:
return FIVE_QUBIT_OP_MAP[ctrl_op_name], 5

# TODO: decompose controls if not built in
raise ValidationError(
Expand Down
28 changes: 28 additions & 0 deletions tests/qasm3/resources/gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import pytest

from pyqasm.maps.gates import (
FIVE_QUBIT_OP_MAP,
FOUR_QUBIT_OP_MAP,
ONE_QUBIT_OP_MAP,
ONE_QUBIT_ROTATION_MAP,
Expand All @@ -42,6 +43,7 @@
| ONE_QUBIT_ROTATION_MAP.keys()
| THREE_QUBIT_OP_MAP.keys()
| FOUR_QUBIT_OP_MAP.keys()
| FIVE_QUBIT_OP_MAP.keys()
)


Expand Down Expand Up @@ -182,6 +184,30 @@ def test_fixture():
locals()[name] = _generate_four_qubit_fixture(gate)


def _generate_five_qubit_fixture(gate_name: str):
@pytest.fixture()
def test_fixture():
if gate_name not in VALID_GATE_NAMES:
raise ValueError(f"Unknown qasm3 gate {gate_name}")
qasm3_string = f"""
OPENQASM 3;
include "stdgates.inc";

qubit[5] q;
{gate_name} q[0], q[1], q[2], q[3], q[4];
{gate_name} q;
"""
return qasm3_string

return test_fixture
Comment on lines +187 to +202

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add docstring and type annotations to _generate_five_qubit_fixture.

As per coding guidelines, every function must have a docstring explaining its purpose, parameters, and return values, and all functions must have type annotations. While this function follows the existing pattern of other fixture generators in the file, it should comply with the project's documented standards.

📚 Proposed enhancement
 def _generate_five_qubit_fixture(gate_name: str):
+    """Generate a pytest fixture for a five-qubit gate.
+
+    Args:
+        gate_name: The name of the five-qubit gate to generate a fixture for.
+
+    Returns:
+        A pytest fixture function that returns a QASM3 string applying the gate
+        to both indexed qubits and a full register.
+    """
     `@pytest.fixture`()
     def test_fixture():
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def _generate_five_qubit_fixture(gate_name: str):
@pytest.fixture()
def test_fixture():
if gate_name not in VALID_GATE_NAMES:
raise ValueError(f"Unknown qasm3 gate {gate_name}")
qasm3_string = f"""
OPENQASM 3;
include "stdgates.inc";
qubit[5] q;
{gate_name} q[0], q[1], q[2], q[3], q[4];
{gate_name} q;
"""
return qasm3_string
return test_fixture
def _generate_five_qubit_fixture(gate_name: str):
"""Generate a pytest fixture for a five-qubit gate.
Args:
gate_name: The name of the five-qubit gate to generate a fixture for.
Returns:
A pytest fixture function that returns a QASM3 string applying the gate
to both indexed qubits and a full register.
"""
`@pytest.fixture`()
def test_fixture():
if gate_name not in VALID_GATE_NAMES:
raise ValueError(f"Unknown qasm3 gate {gate_name}")
qasm3_string = f"""
OPENQASM 3;
include "stdgates.inc";
qubit[5] q;
{gate_name} q[0], q[1], q[2], q[3], q[4];
{gate_name} q;
"""
return qasm3_string
return test_fixture
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/qasm3/resources/gates.py` around lines 187 - 202, Add a docstring to
_generate_five_qubit_fixture explaining that it returns a pytest fixture which
generates a 5-qubit QASM3 string for the given gate_name, describing the
parameter (gate_name: str) and the return (a pytest fixture that returns a str).
Add precise type annotations: _generate_five_qubit_fixture(gate_name: str) ->
Callable[[], str] and annotate the nested test_fixture() -> str. Ensure any
required typing imports (e.g., Callable) are present and keep the existing
runtime validation of gate_name and pytest.fixture decorator.

Source: Coding guidelines



# Generate five-qubit gate fixtures
for gate in FIVE_QUBIT_OP_MAP:
name = _fixture_name(gate)
locals()[name] = _generate_five_qubit_fixture(gate)


def _generate_custom_op_fixture(op_name: str):
print(os.getcwd())

Expand Down Expand Up @@ -244,6 +270,8 @@ def test_fixture():

four_op_tests = [_fixture_name(s) for s in FOUR_QUBIT_OP_MAP]

five_op_tests = [_fixture_name(s) for s in FIVE_QUBIT_OP_MAP]

custom_op_tests = [_fixture_name(s) for s in CUSTOM_OPS]

# qasm_input, expected_error
Expand Down
Loading
Loading