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
52 changes: 40 additions & 12 deletions pulser-core/pulser/_hamiltonian_data/hamiltonian_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,17 @@ def __init__(
# Initializing the samples obj
if not isinstance(samples, SequenceSamples):
raise TypeError(
"The provided sequence has to be a valid "
"SequenceSamples instance."
"The provided samples must be an instance of "
f"'SequenceSamples', not {type(samples)}."
)
if samples.max_duration == 0:
raise ValueError("SequenceSamples is empty.")
# Check compatibility of register and device
if not isinstance(device, BaseDevice):
raise TypeError("The device must be a Device or BaseDevice.")
raise TypeError(
"'device' must be an instance of 'BaseDevice', not "
f"{type(device)}."
)
self._device = device
self.device.validate_register(register)
self._register = register
Expand All @@ -234,14 +237,32 @@ def __init__(
"Samples use SLM mask but device does not have one."
)
if not samples.used_bases <= self.device.supported_bases:
missing_bases = samples.used_bases - self.device.supported_bases
unsupported = [
b
for b in dict.fromkeys(
ch.basis for ch in samples._ch_objs.values()
)
if b in missing_bases
]
supported = list(
dict.fromkeys(ch.basis for ch in self.device.channel_objects)
)
raise ValueError(
"Bases used in samples should be supported by device."
"Bases used in samples must be supported by the device; "
f"{unsupported} not in {supported}."
)
# Check compatibility of masked samples and register
if not samples._slm_mask.targets <= set(self.register.qubits.keys()):
# The mask targets are a set, so there is no caller order to keep
missing_targets = sorted(
samples._slm_mask.targets - set(self.register.qubits.keys()),
key=str,
)
raise ValueError(
"The ids of qubits targeted in SLM mask"
" should be defined in register."
"The ids of qubits targeted in the SLM mask must be defined "
f"in the register; {missing_targets} not in "
f"{list(self.register.qubit_ids)}."
)

self._samples = self._delocalize_samples(samples)
Expand Down Expand Up @@ -281,12 +302,19 @@ def _delocalize_samples(self, samples: SequenceSamples) -> SequenceSamples:
if samples._ch_objs[ch].addressing == "Local":
# Check that targets of Local Channels are defined
# in register
if not set().union(
targets = set().union(
*(slot.targets for slot in ch_samples.slots)
) <= set(self.register.qubits.keys()):
)
if not targets <= set(self.register.qubits.keys()):
# Slot targets are sets: there is no caller order to keep
missing_targets = sorted(
targets - set(self.register.qubits.keys()), key=str
)
raise ValueError(
"The ids of qubits targeted in Local channels"
" should be defined in register."
"The ids of qubits targeted by Local channel "
f"{ch!r} must be defined in the register; "
f"{missing_targets} "
f"not in {list(self.register.qubit_ids)}."
)
samples_list.append(ch_samples)
else:
Expand Down Expand Up @@ -359,8 +387,8 @@ def from_sequence(
"""
if not isinstance(sequence, Sequence):
raise TypeError(
"The provided sequence has to be a valid "
"pulser.Sequence instance."
"'sequence' must be an instance of 'Sequence', not "
f"{type(sequence)}."
)
if sequence.is_parametrized() or sequence.is_register_mappable():
raise ValueError(
Expand Down
67 changes: 46 additions & 21 deletions pulser-simulation/pulser_simulation/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ def __init__(
# Initializing the samples obj
if not isinstance(sampled_seq, SequenceSamples):
raise TypeError(
"The provided sequence has to be a valid "
"SequenceSamples instance."
"The provided samples must be an instance of "
f"'SequenceSamples', not {type(sampled_seq)}."
)
if sampled_seq.max_duration == 0:
raise ValueError("SequenceSamples is empty.")
Expand All @@ -159,14 +159,32 @@ def __init__(
"Samples use SLM mask but device does not have one."
)
if not sampled_seq.used_bases <= device.supported_bases:
missing_bases = sampled_seq.used_bases - device.supported_bases
unsupported = [
b
for b in dict.fromkeys(
ch.basis for ch in sampled_seq._ch_objs.values()
)
if b in missing_bases
]
supported = list(
dict.fromkeys(ch.basis for ch in device.channel_objects)
)
raise ValueError(
"Bases used in samples should be supported by device."
"Bases used in samples must be supported by the device; "
f"{unsupported} not in {supported}."
)
# Check compatibility of masked samples and register
if not sampled_seq._slm_mask.targets <= set(register.qubit_ids):
# The mask targets are a set, so there is no caller order to keep
missing_targets = sorted(
sampled_seq._slm_mask.targets - set(register.qubit_ids),
key=str,
)
raise ValueError(
"The ids of qubits targeted in SLM mask"
" should be defined in register."
"The ids of qubits targeted in the SLM mask must be defined "
f"in the register; {missing_targets} not in "
f"{list(register.qubit_ids)}."
)

self._tot_duration = sampled_seq.max_duration
Expand All @@ -180,9 +198,11 @@ def __init__(
f"{sampling_rate}) must be greater than 0 and "
"less than or equal to 1."
)
if int(self._tot_duration * sampling_rate) < 4:
if (n_points := int(self._tot_duration * sampling_rate)) < 4:
raise ValueError(
"`sampling_rate` is too small, less than 4 data points."
f"'sampling_rate' is too small; {sampling_rate} on a "
f"{self._tot_duration} ns sequence gives {n_points} data "
"points, at least 4 are needed."
)

if noise_model is not None and config is not None:
Expand Down Expand Up @@ -557,14 +577,15 @@ def set_evaluation_times(
eval_times = np.array([])
else:
raise ValueError(
"Wrong evaluation time label. It should "
"be `Full`, `Minimal`, an array of times or"
+ " a float between 0 and 1."
f"Wrong evaluation time label; got {value!r}. It should "
"be `Full`, `Minimal`, an array of times or a float "
"between 0 and 1."
)
elif isinstance(value, float):
if value > 1 or value <= 0:
raise ValueError(
"evaluation_times float must be between 0 and 1."
"'evaluation_times' float must be between 0 and 1; got "
f"{value}."
)
indices = np.linspace(
0,
Expand All @@ -577,20 +598,22 @@ def set_evaluation_times(
elif isinstance(value, (list, tuple, np.ndarray)):
if np.max(value, initial=0) > self._tot_duration * 1e-3:
raise ValueError(
"Provided evaluation-time list extends "
"further than sequence duration."
"Provided evaluation-time list extends further than the "
f"sequence duration; got a maximum of {np.max(value)} µs "
f"for a {self._tot_duration / 1000} µs sequence."
)
if np.min(value, initial=0) < 0:
arr = np.asarray(value)
raise ValueError(
"Provided evaluation-time list contains "
"negative values."
"Provided evaluation-time list contains negative values; "
f"got {arr[arr < 0].tolist()}."
)
eval_times = np.array(value)
else:
raise ValueError(
"Wrong evaluation time label. It should "
"be `Full`, `Minimal`, an array of times or a "
+ "float between 0 and 1."
f"Wrong evaluation time label; got {value!r}. It should "
"be `Full`, `Minimal`, an array of times or a float "
"between 0 and 1."
)
# Ensure 0 and final time are included:
self._eval_times_array = np.union1d(
Expand Down Expand Up @@ -700,7 +723,9 @@ def _run_solver(
elif (progress_bar is False) or (progress_bar is None):
options["progress_bar"] = ""
else:
raise ValueError("`progress_bar` must be a bool.")
raise ValueError(
f"'progress_bar' must be a bool, not {progress_bar!r}."
)

solver_fn: Callable[..., Any] = qutip.sesolve

Expand Down Expand Up @@ -1010,8 +1035,8 @@ def from_sequence(
"""
if not isinstance(sequence, Sequence):
raise TypeError(
"The provided sequence has to be a valid "
"pulser.Sequence instance."
"'sequence' must be an instance of 'Sequence', not "
f"{type(sequence)}."
)
if sequence.is_parametrized() or sequence.is_register_mappable():
raise ValueError(
Expand Down
73 changes: 60 additions & 13 deletions tests/pulser_simulation/test_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,31 @@ def matrices():

def test_initialization_and_construction_of_hamiltonian(seq, mod_device):
fake_sequence = {"pulse1": "fake", "pulse2": "fake"}
with pytest.raises(TypeError, match="sequence has to be a valid"):
with pytest.raises(
TypeError,
match=re.escape(
"'sequence' must be an instance of 'Sequence', not <class 'dict'>."
),
):
QutipEmulator.from_sequence(fake_sequence)
with pytest.raises(TypeError, match="sequence has to be a valid"):
with pytest.raises(
TypeError,
match=re.escape(
"The provided samples must be an instance of 'SequenceSamples', "
"not <class 'dict'>."
),
):
QutipEmulator(
fake_sequence, Register.square(2, prefix="q"), mod_device
)
# Simulation cannot be run on a register not defining "control1"
with pytest.raises(
ValueError,
match="The ids of qubits targeted in Local channels",
match=re.escape(
"The ids of qubits targeted by Local channel 'raman' must be "
"defined in the register; ['control1'] not in "
"['target', 'control2']."
),
):
QutipEmulator(
sampler.sample(seq),
Expand Down Expand Up @@ -188,7 +203,13 @@ def test_initialization_and_construction_of_hamiltonian(seq, mod_device):
"control2": 2,
}

with pytest.raises(ValueError, match="too small, less than"):
with pytest.raises(
ValueError,
match=re.escape(
"'sampling_rate' is too small; 0.0001 on a 9000 ns sequence gives "
"0 data points, at least 4 are needed."
),
):
QutipEmulator.from_sequence(seq, sampling_rate=0.0001)
with pytest.raises(ValueError, match="`sampling_rate`"):
QutipEmulator.from_sequence(seq, sampling_rate=5)
Expand Down Expand Up @@ -400,7 +421,10 @@ def _noise_model(dim):
# seq2 cannot be run on DigitalAnalogDevice because it does not support mw
with pytest.raises(
ValueError,
match="Bases used in samples should be supported by device.",
match=re.escape(
"Bases used in samples must be supported by the device; ['XY'] "
"not in ['ground-rydberg', 'digital']."
),
):
QutipEmulator(sampler.sample(seq2), seq2.register, DigitalAnalogDevice)
sim2 = QutipEmulator.from_sequence(
Expand Down Expand Up @@ -695,7 +719,7 @@ def test_run(seq, patch_plt_show):
sim.run(progress_bar=None)
with pytest.raises(
ValueError,
match="`progress_bar` must be a bool.",
match=re.escape("'progress_bar' must be a bool, not 1."),
):
sim.run(progress_bar=1)

Expand All @@ -720,28 +744,48 @@ def test_run(seq, patch_plt_show):

def test_eval_times(seq):
with pytest.raises(
ValueError, match="evaluation_times float must be between 0 " "and 1."
ValueError,
match=re.escape(
"'evaluation_times' float must be between 0 and 1; got 3.0."
),
):
sim = QutipEmulator.from_sequence(seq, sampling_rate=1.0)
sim.set_evaluation_times(3.0)
with pytest.raises(ValueError, match="Wrong evaluation time label."):
with pytest.raises(
ValueError,
match=re.escape(
"Wrong evaluation time label; got 123. It should be `Full`, "
"`Minimal`, an array of times or a float between 0 and 1."
),
):
sim = QutipEmulator.from_sequence(seq, sampling_rate=1.0)
sim.set_evaluation_times(123)
with pytest.raises(ValueError, match="Wrong evaluation time label."):
with pytest.raises(
ValueError,
match=re.escape(
"Wrong evaluation time label; got 'Best'. It should be `Full`, "
"`Minimal`, an array of times or a float between 0 and 1."
),
):
sim = QutipEmulator.from_sequence(seq, sampling_rate=1.0)
sim.set_evaluation_times("Best")

with pytest.raises(
ValueError,
match="Provided evaluation-time list contains " "negative values.",
match=re.escape(
"Provided evaluation-time list contains negative values; got "
"[-1.0]."
),
):
sim = QutipEmulator.from_sequence(seq, sampling_rate=1.0)
sim.set_evaluation_times([-1, 0, sim.sampling_times[-2]])

with pytest.raises(
ValueError,
match="Provided evaluation-time list extends "
"further than sequence duration.",
match=re.escape(
"Provided evaluation-time list extends further than the sequence "
"duration; got a maximum of 19.0 µs for a 9.0 µs sequence."
),
):
sim = QutipEmulator.from_sequence(seq, sampling_rate=1.0)
sim.set_evaluation_times([0, sim.sampling_times[-1] + 10])
Expand Down Expand Up @@ -1772,7 +1816,10 @@ def test_mask_equals_remove_xy():
# Simulation cannot be run on a register not defining "q2"
with pytest.raises(
ValueError,
match="The ids of qubits targeted in SLM mask",
match=re.escape(
"The ids of qubits targeted in the SLM mask must be defined in "
"the register; ['q2'] not in ['q0', 'q1']."
),
):
QutipEmulator(sampler.sample(seq_masked), reg_two, MockDevice)
# Simulation on reduced register
Expand Down
Loading
Loading