Skip to content

Commit

Permalink
* Changed the carriers argument from a list to a dict.
Browse files Browse the repository at this point in the history
  • Loading branch information
eggerdj committed Jan 20, 2022
1 parent 0639de3 commit c37d941
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 62 deletions.
2 changes: 1 addition & 1 deletion docs/tutorials/qiskit_pulse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ virtual ``Z`` gate is applied.

plt.rcParams["font.size"] = 16

converter = InstructionToSignals(dt, carriers=[w])
converter = InstructionToSignals(dt, carriers={"d0": w})

signals = converter.get_signals(xp)
fig, axs = plt.subplots(1, 2, figsize=(14, 4.5))
Expand Down
51 changes: 9 additions & 42 deletions qiskit_dynamics/pulse/pulse_to_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
SetFrequency,
Waveform,
)
from qiskit import QiskitError
from qiskit_dynamics.signals import DiscreteSignal


Expand All @@ -38,12 +37,11 @@ class InstructionToSignals:
the :meth:`get_signals` method on a schedule. The converter can be initialized
with the optional arguments ``carriers`` and ``channels``. These arguments
change the returned signals of :meth:`get_signals`. When ``channels`` is given
then only the signals specified by name in ``channels`` are returned. If both
`` carriers`` and ``channels`` are given then they must have the same length.
Furthermore, it is understood that the channel named :code:`channels[idx]` has
a carrier frequency given by :code:`carriers[idx]`. Additionally, if the user
specifies ``carriers`` without specifying ``channels`` then the length of
``carriers`` must be as long as the number of channels in the schedule.
then only the signals specified by name in ``channels`` are returned. The
``carriers`` dictionary allows the user to specify the carrier frequency of
the channels. Here, the keys are the channel name, e.g. ``d12`` for drive channel
number 12, and the values are the corresponding frequency. If a channel is not
present in ``carriers`` it is assumed that the carrier frequency is zero.
"""

def __init__(
Expand All @@ -58,38 +56,18 @@ def __init__(
dt: length of the samples. This is required by the converter as pulse
schedule are specified in units of dt and typically do not carry the
value of dt with them.
carriers: a list of carrier frequencies. If it is not None there
must be at least as many carrier frequencies as there are
channels in the schedules that will be converted.
carriers: a dict of carrier frequencies. The keys are the names of the channels
and the values are the corresponding carrier frequency.
channels: A list of channels that the :meth:`get_signals` method should return.
This argument will cause :meth:`get_signals` to return the signals in the
same order as the channels. Channels present in the schedule but absent
from channels will not be included in the returned object. If None is given
(the default) then all channels present in the pulse schedule are returned.
Raises:
QiskitError: If the number of channels and carriers does not match when both are given.
"""

self._dt = dt
self._channels = channels
self._carriers = {}

# If both are given we tie them together in a dict to ensure consistency.
if channels is not None and carriers is not None:
if len(channels) != len(carriers):
raise QiskitError(
"The number of required channels and carries does not match: "
f"len({channels}) != len({carriers})."
)

for idx, chan in enumerate(channels):
self._carriers[chan] = carriers[idx]

# If only carriers is given we map using the index of the carriers.
elif channels is None and carriers is not None:
for idx, carrier in enumerate(carriers):
self._carriers[idx] = carrier
self._carriers = carriers or {}

def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]:
"""
Expand All @@ -103,24 +81,13 @@ def get_signals(self, schedule: Schedule) -> List[DiscreteSignal]:
qiskit.QiskitError: if not enough frequencies supplied
"""

if self._channels is None:
if self._carriers and len(self._carriers) < len(schedule.channels):
raise QiskitError("Not enough carrier frequencies supplied.")

signals, phases, frequency_shifts = {}, {}, {}

for idx, chan in enumerate(schedule.channels):
if self._carriers and not self._channels:
carrier_freq = self._carriers[idx]
elif self._carriers and self._channels:
carrier_freq = self._carriers.get(chan.name, 0.0)
else:
carrier_freq = 0.0

phases[chan.name] = 0.0
frequency_shifts[chan.name] = 0.0
signals[chan.name] = DiscreteSignal(
samples=[], dt=self._dt, name=chan.name, carrier_freq=carrier_freq
samples=[], dt=self._dt, name=chan.name, carrier_freq=self._carriers.get(chan.name, 0.0)
)

for start_sample, inst in schedule.instructions:
Expand Down
29 changes: 10 additions & 19 deletions test/dynamics/signals/test_pulse_to_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def test_carriers_and_dt(self):
sched = Schedule(name="Schedule")
sched += Play(Gaussian(duration=20, amp=0.5, sigma=4), DriveChannel(0))

converter = InstructionToSignals(dt=0.222, carriers=[5.5e9])
converter = InstructionToSignals(dt=0.222, carriers={"d0":5.5e9})
signals = converter.get_signals(sched)

self.assertEqual(signals[0].carrier_freq, 5.5e9)
Expand All @@ -103,7 +103,7 @@ def test_shift_frequency(self):
sched += ShiftFrequency(1.0, DriveChannel(0))
sched += Play(Constant(duration=10, amp=1.0), DriveChannel(0))

converter = InstructionToSignals(dt=0.222, carriers=[5.0])
converter = InstructionToSignals(dt=0.222, carriers={"d0": 5.0})
signals = converter.get_signals(sched)

for idx in range(10):
Expand All @@ -116,7 +116,7 @@ def test_set_frequency(self):
sched += SetFrequency(4.0, DriveChannel(0))
sched += Play(Constant(duration=10, amp=1.0), DriveChannel(0))

converter = InstructionToSignals(dt=0.222, carriers=[5.0])
converter = InstructionToSignals(dt=0.222, carriers={"d0": 5.0})
signals = converter.get_signals(sched)

for idx in range(10):
Expand All @@ -129,7 +129,7 @@ def test_uneven_pulse_length(self):
schedule |= Play(Waveform(np.ones(10)), DriveChannel(0))
schedule += Play(Constant(20, 1), DriveChannel(1))

converter = InstructionToSignals(dt=0.1, carriers=[2.0, 3.0])
converter = InstructionToSignals(dt=0.1, carriers={"d0": 2.0, "d1": 3.0})

signals = converter.get_signals(schedule)

Expand Down Expand Up @@ -171,11 +171,11 @@ def setUp(self):

@unpack
@data(
([5.0, 5.1, 5.0, 5.1], ["d0", "d2", "u0", "u1"]),
([5.0, 5.1, 5.0, 5.1], ["m0", "m1", "m2", "m3"]),
([5.0, 5.1, 5.0, 5.1], ["m0", "m1", "d0", "d1"]),
([5.0], ["d1"]),
([5.0], ["d123"]),
({"d0": 5.0, "d2": 5.1, "u0": 5.0, "u1": 5.1}, ["d0", "d2", "u0", "u1"]),
({"m0": 5.0, "m1": 5.1, "m2": 5.0, "m3": 5.1}, ["m0", "m1", "m2", "m3"]),
({"m0": 5.0, "m1": 5.1, "d0": 5.0, "d1": 5.1}, ["m0", "m1", "d0", "d1"]),
({"d1": 5.0}, ["d1"]),
({"d123": 5.0}, ["d123"]),
)
def test_channel_combinations(self, carriers, channels):
"""Test that we can filter out channels in the right order and number."""
Expand All @@ -191,18 +191,9 @@ def test_channel_combinations(self, carriers, channels):
def test_empty_signal(self):
"""Test that requesting a channel that is not in the schedule gives and empty signal."""

converter = InstructionToSignals(dt=0.222, carriers=[1.0], channels=["d123"])
converter = InstructionToSignals(dt=0.222, carriers={"d123": 1.0}, channels=["d123"])

signals = converter.get_signals(self._schedule)

self.assertEqual(len(signals), 1)
self.assertEqual(signals[0].duration, 0)

def test_malformed_input_args(self):
"""Test that we get errors if the carriers and channels do not match."""

with self.assertRaises(QiskitError):
InstructionToSignals(dt=1, carriers=[1], channels=["d0", "d1"])

with self.assertRaises(QiskitError):
InstructionToSignals(dt=1, carriers=[1, 2], channels=["d0"])

0 comments on commit c37d941

Please sign in to comment.