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

Impose qubit order in MappableRegister #507

Merged
merged 10 commits into from
May 2, 2023
9 changes: 1 addition & 8 deletions pulser-core/pulser/json/abstract_repr/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,14 +153,7 @@ def convert_targets(
og_dim = target_array.ndim
if og_dim == 0:
target_array = target_array[np.newaxis]
try:
indices = seq.register.find_indices(target_array.tolist())
# RuntimeError raised when calling seq.register for a MappableRegister
except RuntimeError:
raise NotImplementedError(
"Serialization of sequences with local operations and"
" a mappable register is currently not supported."
)
indices = seq._register.find_indices(target_array.tolist())
HGSilveri marked this conversation as resolved.
Show resolved Hide resolved
return indices[0] if og_dim == 0 else indices

def get_kwarg_default(call_name: str, kwarg_name: str) -> Any:
Expand Down
37 changes: 15 additions & 22 deletions pulser-core/pulser/register/mappable_reg.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ def build_register(self, qubits: Mapping[QubitId, int]) -> BaseRegister:
raise ValueError(
"All qubits must be labeled with pre-declared qubit IDs."
)
elif set(chosen_ids) != set(self.qubit_ids[: len(chosen_ids)]):
raise ValueError(
f"'qubits' should contain the {len(qubits.keys())} first "
"elements of the 'qubit_ids'."
)
HGSilveri marked this conversation as resolved.
Show resolved Hide resolved
register_ordered_qubits = {
id: qubits[id] for id in self._qubit_ids if id in chosen_ids
}
Expand All @@ -80,10 +85,8 @@ def build_register(self, qubits: Mapping[QubitId, int]) -> BaseRegister:
qubit_ids=tuple(register_ordered_qubits.keys()),
)

def find_indices(
self, chosen_ids: set[QubitId], id_list: abcSequence[QubitId]
) -> list[int]:
"""Computes indices of qubits according to a register mapping.
def find_indices(self, id_list: abcSequence[QubitId]) -> list[int]:
"""Computes indices of qubits.

This can especially be useful when building a Pulser Sequence
with a parameter denoting qubits.
Expand All @@ -92,41 +95,31 @@ def find_indices(
Let ``reg`` be a mappable register with qubit Ids "a", "b", "c"
and "d".

>>> qubit_map = dict(b=1, a=2, d=0)
>>> reg.find_indices(
>>> qubit_map.keys(),
>>> ["a", "b", "d", "a"])
>>> reg.find_indices(["a", "b", "d", "a"])

It returns ``[0, 1, 2, 0]``, following the qubits order of the
mappable register, but keeping only the chosen ones.
It returns ``[0, 1, 3, 0]``, following the qubits order of the
mappable register (defined by qubit_ids).

Then, it is possible to use these indices when building a
sequence, typically to instanciate an array of variables
that can be provided as an argument to ``target_index``
and ``phase_shift_index``.

``qubit_map`` should be provided when building the sequence,
to tell how to instantiate the register from the mappable register.
When building a sequence and declaring N qubits, their ids should
refer to the first N elements of qubit_id.

Args:
chosen_ids: IDs of the qubits that are chosen to
map the MappableRegister
id_list: IDs of the qubits to denote.

Returns:
Indices of the qubits to denote, only valid for the
given mapping.
"""
if not chosen_ids <= set(self._qubit_ids):
raise ValueError(
"Chosen IDs must be selected among pre-declared qubit IDs."
)
if not set(id_list) <= chosen_ids:
if not set(id_list) <= set(self._qubit_ids):
raise ValueError(
"The IDs list must be selected among the chosen IDs."
"The IDs list must be selected among pre-declared qubit IDs."
)
ordered_ids = [id for id in self.qubit_ids if id in chosen_ids]
return [ordered_ids.index(id) for id in id_list]
return [self.qubit_ids.index(id) for id in id_list]

def _to_dict(self) -> dict[str, Any]:
return obj_to_dict(self, self._layout, *self._qubit_ids)
Expand Down
15 changes: 0 additions & 15 deletions pulser-core/pulser/sequence/_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,21 +97,6 @@ def wrapper(self: Sequence, *args: Any, **kwargs: Any) -> Any:
return cast(F, wrapper)


def check_allow_qubit_index(func: F) -> F:
"""Checks if using qubit indices is allowed."""

@wraps(func)
def wrapper(self: Sequence, *args: Any, **kwargs: Any) -> Any:
if not self.is_parametrized() and self.is_register_mappable():
raise RuntimeError(
f"Sequence.{func.__name__} cannot be called in"
" non-parametrized sequences using a mappable register."
)
func(self, *args, **kwargs)

return cast(F, wrapper)


def mark_non_empty(func: F) -> F:
"""Marks the sequence as non-empty."""

Expand Down
10 changes: 10 additions & 0 deletions pulser-core/pulser/sequence/_seq_str.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"""Function for representing the sequence in a string."""
from __future__ import annotations

import warnings
from typing import TYPE_CHECKING

from pulser.pulse import Pulse
Expand All @@ -30,6 +31,15 @@ def seq_to_str(sequence: Sequence) -> str:
delay_line = "t: {}->{} | Delay \n"
det_delay_line = "t: {}->{} | Detuned Delay | Detuning: {:.3g} rad/µs\n"
for ch, seq in sequence._schedule.items():
if (
seq.channel_obj.addressing == "Global"
and sequence.is_register_mappable()
):
warnings.warn(
"Showing the register for a sequence with a mappable register."
f"Target qubits of channel {ch} will be defined in build.",
UserWarning,
)
basis = sequence.declared_channels[ch].basis
full += f"Channel: {ch}\n"
first_slot = True
Expand Down
6 changes: 3 additions & 3 deletions pulser-core/pulser/sequence/sequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -959,7 +959,6 @@ def target(
self._target(qubits, channel)

@seq_decorators.store
@seq_decorators.check_allow_qubit_index
def target_index(
self,
qubits: Union[int, Iterable[int], Parametrized],
Expand Down Expand Up @@ -1055,7 +1054,6 @@ def phase_shift(
self._phase_shift(phi, *targets, basis=basis)

@seq_decorators.store
@seq_decorators.check_allow_qubit_index
def phase_shift_index(
self,
phi: Union[float, Parametrized],
Expand Down Expand Up @@ -1505,7 +1503,9 @@ def _check_qubits_give_ids(
else:
qubits = cast(Tuple[int, ...], qubits)
try:
return {self.register.qubit_ids[index] for index in qubits}
return {
self._register.qubit_ids[index] for index in qubits
}
except IndexError:
raise IndexError("Indices must exist for the register.")
ids = set(cast(Tuple[QubitId, ...], qubits))
Expand Down
19 changes: 13 additions & 6 deletions tests/test_abstract_repr.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,13 +625,21 @@ def test_mappable_register(self, triangular_lattice):
):
seq.to_abstract_repr(var=0)

with pytest.raises(
ValueError,
match="The given 'defaults' produce an invalid sequence.",
):
seq.to_abstract_repr(var=0, qubits={"q1": 0})

with pytest.raises(TypeError, match="Did not receive values"):
seq.to_abstract_repr(qubits={"q1": 0})
seq.to_abstract_repr(qubits={"q0": 0})

assert not seq.is_parametrized()

abstract = json.loads(seq.to_abstract_repr(var=0, qubits={"q1": 0}))
abstract = json.loads(seq.to_abstract_repr(var=0, qubits={"q0": 0}))
assert abstract["register"] == [
{"qid": "q0"},
{"qid": "q1", "default_trap": 0},
{"qid": "q0", "default_trap": 0},
{"qid": "q1"},
]
assert abstract["variables"]["var"] == dict(type="int", value=[0])

Expand Down Expand Up @@ -722,7 +730,6 @@ def test_default_basis(
"basis", "ground-rydberg"
)

@pytest.mark.xfail(reason="Can't get index of mappable register qubits.")
@pytest.mark.parametrize(
"op,args",
[
Expand Down Expand Up @@ -875,7 +882,7 @@ def test_deserialize_register(self, layout_coords):
def test_deserialize_mappable_register(self):
layout_coords = (5 * np.arange(8)).reshape((4, 2))
s = _get_serialized_seq(
register=[{"qid": "q0"}, {"qid": "q1", "default_trap": 2}],
register=[{"qid": "q0", "default_trap": 2}, {"qid": "q1"}],
layout={
"coordinates": layout_coords.tolist(),
"slug": "test_layout",
Expand Down
23 changes: 7 additions & 16 deletions tests/test_register_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,35 +226,26 @@ def test_mappable_register_creation():
mapp_reg = tri.make_mappable_register(5)
assert mapp_reg.qubit_ids == ("q0", "q1", "q2", "q3", "q4")

assert mapp_reg.find_indices(
{"q2", "q4", "q1"}, ["q4", "q2", "q1", "q2"]
) == [2, 1, 0, 1]
assert mapp_reg.find_indices(["q4", "q2", "q1", "q2"]) == [4, 2, 1, 2]

with pytest.raises(
ValueError, match="must be selected among pre-declared qubit IDs"
):
mapp_reg.find_indices(
{"q2", "q4", "q1", "q5"}, ["q4", "q2", "q1", "q2"]
)

with pytest.raises(
ValueError, match="must be selected among the chosen IDs"
):
mapp_reg.find_indices(
{"q2", "q4", "q1"}, ["q4", "q2", "q1", "q2", "q3"]
)
mapp_reg.find_indices(["q4", "q2", "q1", "q5"])

with pytest.raises(
ValueError, match="labeled with pre-declared qubit IDs"
):
mapp_reg.build_register({"q0": 0, "q5": 2})
with pytest.raises(
ValueError, match="'qubits' should contain the 2 first elements"
):
mapp_reg.build_register({"q0": 0, "q2": 2})

qubit_map = {"q0": 10, "q1": 49}
reg = mapp_reg.build_register(qubit_map)
assert reg == Register(
{"q0": tri.traps_dict[10], "q1": tri.traps_dict[49]}
)
names = ["q1", "q0", "q0"]
assert mapp_reg.find_indices(qubit_map.keys(), names) == reg.find_indices(
names
)
assert mapp_reg.find_indices(names) == reg.find_indices(names)
Loading