Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for allocating qubits in decompose to cirq.unitary #6112

Merged
merged 13 commits into from
Jun 5, 2023
Next Next commit
Add support for allocating qubits in decompose to cirq.unitary
  • Loading branch information
NoureldinYosri committed May 30, 2023
commit af8a5dd6c68a9cd6f7f573dc80b52ce27974846e
19 changes: 17 additions & 2 deletions cirq-core/cirq/protocols/unitary_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,15 +179,30 @@ def _strat_unitary_from_decompose(val: Any) -> Optional[np.ndarray]:
if operations is None:
return NotImplemented

all_qubits = frozenset(q for op in operations for q in op.qubits)
work_qubits = frozenset(qubits)
ancillas = tuple(sorted(q for q in all_qubits if q not in work_qubits))
NoureldinYosri marked this conversation as resolved.
Show resolved Hide resolved

ordered_qubits = ancillas + tuple(qubits)
val_qid_shape = (2,) * len(
ancillas
) + val_qid_shape # For now ancillas have only one qid_shape = (2,).
NoureldinYosri marked this conversation as resolved.
Show resolved Hide resolved
NoureldinYosri marked this conversation as resolved.
Show resolved Hide resolved

# Apply sub-operations' unitary effects to an identity matrix.
state = qis.eye_tensor(val_qid_shape, dtype=np.complex128)
buffer = np.empty_like(state)
result = apply_unitaries(
operations, qubits, ApplyUnitaryArgs(state, buffer, range(len(val_qid_shape))), None
operations, ordered_qubits, ApplyUnitaryArgs(state, buffer, range(len(val_qid_shape))), None
)

# Package result.
if result is None:
return None

state_len = np.prod(val_qid_shape, dtype=np.int64)
return result.reshape((state_len, state_len))
work_state_len = np.prod(val_qid_shape[len(ancillas) :], dtype=np.int64)
result = result.reshape((state_len, state_len))
NoureldinYosri marked this conversation as resolved.
Show resolved Hide resolved
# Assuming borrowable qubits are restored to their original state and
# clean qubits restord to the zero state then the desired unitary is
# the upper left square.
return result[:work_state_len, :work_state_len]
51 changes: 51 additions & 0 deletions cirq-core/cirq/protocols/unitary_protocol_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,40 @@ def _decompose_(self, qubits):
yield FullyImplemented(self.unitary_value).on(qubits[0])


class GateThatAllocatesAQubit(cirq.Gate):
_target_unitary = np.array([[1, 0], [0, -1]])

def _num_qubits_(self):
return 1

def _decompose_(self, q):
anc = cirq.NamedQubit("anc")
yield cirq.CX(*q, anc)
yield (cirq.Z)(anc)
yield cirq.CX(*q, anc)


class GateThatAllocatesTwoQubits(cirq.Gate):
# Unitary = (-j I_2) \otimes Z
_target_unitary = np.array([[-1j, 0, 0, 0], [0, 1j, 0, 0], [0, 0, 1j, 0], [0, 0, 0, -1j]])

def _num_qubits_(self):
return 2

def _decompose_(self, qs):
q0, q1 = qs
anc = cirq.NamedQubit.range(2, prefix='two_ancillas_')

yield cirq.X(anc[0])
yield cirq.CX(q0, anc[0])
yield (cirq.Y)(anc[0])
yield cirq.CX(q0, anc[0])

yield cirq.CX(q1, anc[1])
yield (cirq.Z)(anc[1])
yield cirq.CX(q1, anc[1])


class DecomposableOperation(cirq.Operation):
qubits = ()
with_qubits = NotImplemented
Expand Down Expand Up @@ -201,6 +235,23 @@ def test_decompose_and_get_unitary():
np.testing.assert_allclose(_strat_unitary_from_decompose(DummyComposite()), np.eye(1))
np.testing.assert_allclose(_strat_unitary_from_decompose(OtherComposite()), m2)

np.testing.assert_allclose(
_strat_unitary_from_decompose(GateThatAllocatesAQubit()),
GateThatAllocatesAQubit._target_unitary,
)
NoureldinYosri marked this conversation as resolved.
Show resolved Hide resolved
np.testing.assert_allclose(
_strat_unitary_from_decompose(GateThatAllocatesAQubit().on(a)),
GateThatAllocatesAQubit._target_unitary,
)
np.testing.assert_allclose(
_strat_unitary_from_decompose(GateThatAllocatesTwoQubits()),
GateThatAllocatesTwoQubits._target_unitary,
)
np.testing.assert_allclose(
_strat_unitary_from_decompose(GateThatAllocatesTwoQubits().on(a, b)),
GateThatAllocatesTwoQubits._target_unitary,
)


def test_decomposed_has_unitary():
# Gates
Expand Down