Skip to content
Merged
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
4 changes: 4 additions & 0 deletions python/pecos-rslib/pecos_rslib.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1770,6 +1770,7 @@ class GateType:
RXX: GateType
RYY: GateType
RZZ: GateType
RXYXY2Q: GateType
RXY1Q: GateType
U: GateType
F: GateType
Expand Down Expand Up @@ -1836,6 +1837,8 @@ class Gate:
@staticmethod
def cz(pairs: Sequence[tuple[int, int]]) -> Gate: ...
@staticmethod
def rxyxy2q(theta: Any, phi: Any, pairs: Sequence[tuple[int, int]]) -> Gate: ...
@staticmethod
def mx(qubits: Sequence[int]) -> Gate: ...
@staticmethod
def mz(qubits: Sequence[int]) -> Gate: ...
Expand Down Expand Up @@ -1923,6 +1926,7 @@ class TickHandle:
def rxx(self, theta: Any, pairs: Sequence[tuple[int, int]]) -> TickHandle: ...
def ryy(self, theta: Any, pairs: Sequence[tuple[int, int]]) -> TickHandle: ...
def rzz(self, theta: Any, pairs: Sequence[tuple[int, int]]) -> TickHandle: ...
def rxyxy2q(self, theta: Any, phi: Any, pairs: Sequence[tuple[int, int]]) -> TickHandle: ...
def add_gate(
self,
name: str,
Expand Down
29 changes: 29 additions & 0 deletions python/pecos-rslib/src/dag_circuit_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,14 @@ impl PyGateType {
}
}

#[classattr]
#[pyo3(name = "RXYXY2Q")]
fn rxyxy2q_attr() -> Self {
Self {
inner: GateType::RXYXY2Q,
}
}

#[classattr]
#[pyo3(name = "RXY1Q")]
fn rxy1q() -> Self {
Expand Down Expand Up @@ -901,6 +909,14 @@ impl PyGate {
}
}

/// Create an RXYXY2Q gate.
#[staticmethod]
fn rxyxy2q(theta: AngleParam, phi: AngleParam, pairs: Vec<(usize, usize)>) -> Self {
Self {
inner: Gate::rxyxy2q(theta.0, phi.0, &pairs),
}
}

/// Create an RXY1Q gate.
#[staticmethod]
fn rxy1q(theta: AngleParam, phi: AngleParam, qubits: Vec<usize>) -> Self {
Expand Down Expand Up @@ -3633,6 +3649,19 @@ impl PyTickHandle {
Ok(slf)
}

/// Apply an RXYXY2Q rotation.
fn rxyxy2q(
slf: Py<Self>,
py: Python<'_>,
theta: AngleParam,
phi: AngleParam,
pairs: Vec<(usize, usize)>,
) -> PyResult<Py<Self>> {
slf.borrow_mut(py)
.add_gate_internal(py, Gate::rxyxy2q(theta.0, phi.0, &pairs))?;
Ok(slf)
}

// --- Generic gate dispatch (name-based) ---

/// Add a gate by name, resolving to a native `GateType` if possible.
Expand Down
10 changes: 10 additions & 0 deletions python/quantum-pecos/src/pecos/_qis_trace_replay.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,13 @@ def tuple_args(payload: object, op_name: str, arity: int) -> tuple[Any, ...]:
float(theta),
[(mapped_slot(int(qubit_a), op_name), mapped_slot(int(qubit_b), op_name))],
)
elif op_name == "RXYXY2Q":
theta, phi, qubit_a, qubit_b = tuple_args(payload, op_name, 4)
tick.rxyxy2q(
float(theta),
float(phi),
[(mapped_slot(int(qubit_a), op_name), mapped_slot(int(qubit_b), op_name))],
)
elif op_name in {"Measure", "MeasureLeaked"}:
program_id, result_id = tuple_args(payload, op_name, 2)
measurement_qubit = mapped_slot(int(program_id), op_name)
Expand Down Expand Up @@ -440,6 +447,9 @@ def _replay_lowered_qis_trace_into_tick_circuit(
elif gate_type == "RZZ":
(theta,) = _require_gate_angles(angles, gate_type, 1)
tick.rzz(theta, _gate_pairs(qubits, gate_type))
elif gate_type == "RXYXY2Q":
theta, phi = _require_gate_angles(angles, gate_type, 2)
tick.rxyxy2q(theta, phi, _gate_pairs(qubits, gate_type))
elif gate_type == "CCX":
tick.ccx(_gate_triples(qubits, gate_type))
else:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"RXX",
"RYY",
"RZZ",
"RXYXY2Q",
"CX",
"SXX",
"SXXdg",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"RXX",
"RYY",
"RZZ",
"RXYXY2Q",
"CX",
"SXX",
"SXXdg",
Expand Down
1 change: 1 addition & 0 deletions python/quantum-pecos/src/pecos/quantum/gate_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"RXX",
"RYY",
"RZZ",
"RXYXY2Q",
"RXXRYYRZZ",
}

Expand Down
81 changes: 80 additions & 1 deletion python/quantum-pecos/tests/pecos/test_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import pecos
import pecos_rslib
import pytest
from pecos._qis_trace_replay import _replay_qis_trace_into_tick_circuit
from pecos.quantum import TickCircuit
from pecos.simulators import StateVec

Expand Down Expand Up @@ -142,6 +143,81 @@ def test_qis_trace_crz_preserves_full_matrix() -> None:
assert abs(columns[column][row] - reference[row][column]) < 1e-12


@pytest.mark.parametrize("lowered", [False, True], ids=["raw", "runtime-lowered"])
def test_qis_trace_rxyxy2q_preserves_angles_and_pair(lowered: bool) -> None:
operations = [
{"AllocateQubit": {"id": 7}},
{"AllocateQubit": {"id": 11}},
{"AllocateQubit": {"id": 3}},
{"Quantum": {"RXYXY2Q": [-0.73, 0.41, 3, 7]}},
]
if lowered:
trace = _completed_trace()
trace[0]["operations"] = operations
trace[0]["num_operations"] = len(operations)
trace[0]["lowered_quantum_ops"] = [
{
"gate_type": "RXYXY2Q",
"qubits": [2, 0],
"angles": [-0.73, 0.41],
"params": [],
"metadata": {"source_label": "xyxy"},
},
]
circuit = pecos.qis_operation_trace_to_tick_circuit(trace)
assert circuit.get_gate_meta(0, 0, "source_label") == "xyxy"
else:
circuit = _replay_qis_trace_into_tick_circuit(operations)

gates = [gate for _, gate in circuit.gate_batches() if gate.gate_type.name != "PZ"]
assert len(gates) == 1
assert gates[0].gate_type.name == "RXYXY2Q"
# Gate.angles exposes Angle64's unsigned radians; compare signed representatives.
assert [math.remainder(angle, math.tau) for angle in gates[0].angles] == pytest.approx([-0.73, 0.41])
assert gates[0].qubits == [2, 0]


@pytest.mark.parametrize("typed_angles", [False, True], ids=["float", "angle64"])
def test_tick_circuit_rxyxy2q_constructor(typed_angles: bool) -> None:
theta, phi = -0.73, 0.41
if typed_angles:
theta, phi = (pecos_rslib.angle64.from_radians(angle) for angle in (theta, phi))
circuit = TickCircuit()
tick = circuit.tick()
assert tick.rxyxy2q(theta, phi, [(5, 2)]) is tick
assert circuit.gate_count() == 1
gates = [circuit.get_tick(0).gate_batches()[0], pecos_rslib.Gate.rxyxy2q(theta, phi, [(5, 2)])]
for gate in gates:
assert gate.gate_type.name == "RXYXY2Q"
assert gate.gate_type == pecos_rslib.GateType.RXYXY2Q
assert [math.remainder(angle, math.tau) for angle in gate.angles] == pytest.approx([-0.73, 0.41])
assert gate.qubits == [5, 2]


@pytest.mark.parametrize("lowered", [False, True], ids=["raw", "runtime-lowered"])
def test_qis_trace_unknown_gate_still_fails(lowered: bool) -> None:
if lowered:
trace = _completed_trace()
trace[0]["lowered_quantum_ops"][1]["gate_type"] = "UnknownGate"
with pytest.raises(ValueError, match=r"Unsupported.*UnknownGate"):
pecos.qis_operation_trace_to_tick_circuit(trace)
else:
with pytest.raises(ValueError, match=r"Unsupported.*UnknownGate"):
_replay_qis_trace_into_tick_circuit([{"Quantum": {"UnknownGate": 0}}])


def test_qis_trace_rxyxy2q_rejects_incomplete_pair() -> None:
trace = _completed_trace()
trace[0]["lowered_quantum_ops"][1] = {
"gate_type": "RXYXY2Q",
"qubits": [2],
"angles": [-0.73, 0.41],
"params": [],
}
with pytest.raises(ValueError, match=r"RXYXY2Q.*expected an even number of qubits"):
pecos.qis_operation_trace_to_tick_circuit(trace)


def test_tracing_apis_are_exported_at_top_level() -> None:
assert pecos.capture_qis_operation_trace is pecos.tracing.capture_qis_operation_trace
assert pecos.qis_operation_trace_to_tick_circuit is pecos.tracing.qis_operation_trace_to_tick_circuit
Expand Down Expand Up @@ -286,6 +362,9 @@ def test_qis_operation_trace_conversion_rejects_boolean_framing_counts() -> None
("RXY1Q", [0.5]),
("CRZ", []),
("RZZ", []),
("RXYXY2Q", []),
("RXYXY2Q", [0.5]),
("RXYXY2Q", [0.5, 0.25, 0.75]),
],
)
def test_qis_operation_trace_conversion_rejects_invalid_angle_arity(
Expand All @@ -295,7 +374,7 @@ def test_qis_operation_trace_conversion_rejects_invalid_angle_arity(
trace = _completed_trace()
trace[0]["lowered_quantum_ops"][1] = {
"gate_type": gate_type,
"qubits": [0, 1] if gate_type in {"CRZ", "RZZ"} else [0],
"qubits": [0, 1] if gate_type in {"CRZ", "RZZ", "RXYXY2Q"} else [0],
"angles": angles,
"params": [],
}
Expand Down
Loading