From d4bbaf605c985d66467fbc5460ba17bf6f7cdcbe Mon Sep 17 00:00:00 2001 From: SooluThomas Date: Fri, 15 May 2020 15:03:16 -0400 Subject: [PATCH] Add ShiftFrequency instruction to pulse (#4390) * Add ShiftFrequency * Review suggestions * Lint * remove unwanted newlines * lint * add newline * Review suggestions and bugfix * Reno * Hz -> GHz conversion for ShiftFrequency -> PulseQobjInstruction * Logic fixes * review suggestion * review suggestions Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- qiskit/pulse/__init__.py | 3 +- qiskit/pulse/instructions/__init__.py | 3 +- qiskit/pulse/instructions/frequency.py | 31 +++++++++++++ qiskit/qobj/converters/pulse_instruction.py | 44 +++++++++++++++++++ qiskit/visualization/pulse/matplotlib.py | 12 ++++- ...ment-shift-frequency-46d4ea16d8be2f58.yaml | 10 +++++ test/python/pulse/test_schedule.py | 27 ++++++++---- test/python/qobj/test_pulse_converter.py | 30 ++++++++++++- test/python/qobj/test_qobj.py | 2 + .../test_pulse_visualization_output.py | 3 +- 10 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 releasenotes/notes/implement-shift-frequency-46d4ea16d8be2f58.yaml diff --git a/qiskit/pulse/__init__.py b/qiskit/pulse/__init__.py index a7b3814c7cbd..8feb8e203d48 100644 --- a/qiskit/pulse/__init__.py +++ b/qiskit/pulse/__init__.py @@ -137,7 +137,8 @@ from .configuration import LoConfig, LoRange, Kernel, Discriminator from .exceptions import PulseError from .instruction_schedule_map import InstructionScheduleMap -from .instructions import Acquire, Instruction, Delay, Play, ShiftPhase, Snapshot, SetFrequency +from .instructions import (Acquire, Instruction, Delay, Play, ShiftPhase, Snapshot, + SetFrequency, ShiftFrequency) from .interfaces import ScheduleComponent from .pulse_lib import (SamplePulse, Gaussian, GaussianSquare, Drag, Constant, ConstantPulse, ParametricPulse) diff --git a/qiskit/pulse/instructions/__init__.py b/qiskit/pulse/instructions/__init__.py index 11fbea361436..e26d7269f3e7 100644 --- a/qiskit/pulse/instructions/__init__.py +++ b/qiskit/pulse/instructions/__init__.py @@ -39,6 +39,7 @@ Delay Play SetFrequency + ShiftFrequency ShiftPhase Snapshot @@ -53,7 +54,7 @@ from .acquire import Acquire from .delay import Delay from .instruction import Instruction -from .frequency import SetFrequency +from .frequency import SetFrequency, ShiftFrequency from .phase import ShiftPhase from .play import Play from .snapshot import Snapshot diff --git a/qiskit/pulse/instructions/frequency.py b/qiskit/pulse/instructions/frequency.py index 3e603a83ebb1..e5a90ec77cad 100644 --- a/qiskit/pulse/instructions/frequency.py +++ b/qiskit/pulse/instructions/frequency.py @@ -60,3 +60,34 @@ def channel(self) -> PulseChannel: scheduled on. """ return self._channel + + +class ShiftFrequency(Instruction): + """Shift the channel frequency away from the current frequency.""" + + def __init__(self, + frequency: float, + channel: PulseChannel, + name: Optional[str] = None): + """Creates a new shift frequency instruction. + + Args: + frequency: Frequency shift of the channel in Hz. + channel: The channel this instruction operates on. + name: Name of this set channel frequency command. + """ + self._frequency = float(frequency) + self._channel = channel + super().__init__((frequency, channel), 0, (channel,), name=name) + + @property + def frequency(self) -> float: + """Frequency shift from the set frequency.""" + return self._frequency + + @property + def channel(self) -> PulseChannel: + """Return the :py:class:`~qiskit.pulse.channels.Channel` that this instruction is + scheduled on. + """ + return self._channel diff --git a/qiskit/qobj/converters/pulse_instruction.py b/qiskit/qobj/converters/pulse_instruction.py index 661127b67eae..7cd51d4f2ecc 100644 --- a/qiskit/qobj/converters/pulse_instruction.py +++ b/qiskit/qobj/converters/pulse_instruction.py @@ -273,6 +273,25 @@ def convert_set_frequency(self, shift, instruction): } return self._qobj_model(**command_dict) + @bind_instruction(instructions.ShiftFrequency) + def convert_shift_frequency(self, shift, instruction): + """Return converted `ShiftFrequency`. + + Args: + shift (int): Offset time. + instruction (ShiftFrequency): Shift frequency instruction. + + Returns: + dict: Dictionary of required parameters. + """ + command_dict = { + 'name': 'shiftf', + 't0': shift+instruction.start_time, + 'ch': instruction.channel.name, + 'frequency': instruction.frequency / 1e9 + } + return self._qobj_model(**command_dict) + @bind_instruction(instructions.ShiftPhase) def convert_shift_phase(self, shift, instruction): """Return converted `ShiftPhase`. @@ -545,6 +564,31 @@ def gen_sf_schedule(*args, **kwargs): return instructions.SetFrequency(frequency, channel) << t0 + @bind_name('shiftf') + def convert_shift_frequency(self, instruction): + """Return converted `ShiftFrequency`. + + Args: + instruction (PulseQobjInstruction): Shift frequency qobj instruction. + + Returns: + Schedule: Converted and scheduled Instruction + """ + t0 = instruction.t0 + channel = self.get_channel(instruction.ch) + frequency = instruction.frequency * 1e9 + + if isinstance(frequency, str): + frequency_expr = parse_string_expr(frequency, partial_binding=False) + + def gen_sf_schedule(*args, **kwargs): + _frequency = frequency_expr(*args, **kwargs) + return instructions.ShiftFrequency(_frequency, channel) << t0 + + return ParameterizedSchedule(gen_sf_schedule, parameters=frequency_expr.params) + + return instructions.ShiftFrequency(frequency, channel) << t0 + @bind_name('delay') def convert_delay(self, instruction): """Return converted `Delay`. diff --git a/qiskit/visualization/pulse/matplotlib.py b/qiskit/visualization/pulse/matplotlib.py index c080ac18e075..9da8325f81e5 100644 --- a/qiskit/visualization/pulse/matplotlib.py +++ b/qiskit/visualization/pulse/matplotlib.py @@ -36,7 +36,7 @@ from qiskit.pulse.commands import FrameChangeInstruction from qiskit.pulse import (SamplePulse, FrameChange, PersistentValue, Snapshot, Play, Acquire, PulseError, ParametricPulse, SetFrequency, ShiftPhase, - Instruction, ScheduleComponent) + Instruction, ScheduleComponent, ShiftFrequency) class EventsOutputChannels: @@ -105,6 +105,14 @@ def frequencychanges(self) -> Dict[int, SetFrequency]: return self._trim(self._frequencychanges) + @property + def frequencyshift(self) -> Dict[int, ShiftFrequency]: + """Set the frequency changes.""" + if self._frequencychanges is None: + self._build_waveform() + + return self._trim(self._frequencychanges) + @property def conditionals(self) -> Dict[int, str]: """Get conditionals.""" @@ -194,6 +202,8 @@ def _build_waveform(self): pv[time:] = 0 elif isinstance(command, SetFrequency): tmp_sf = command.frequency + elif isinstance(command, ShiftFrequency): + tmp_sf = command.frequency elif isinstance(command, Snapshot): self._snapshots[time] = command.name if tmp_fc != 0: diff --git a/releasenotes/notes/implement-shift-frequency-46d4ea16d8be2f58.yaml b/releasenotes/notes/implement-shift-frequency-46d4ea16d8be2f58.yaml new file mode 100644 index 000000000000..c5e264ab3c74 --- /dev/null +++ b/releasenotes/notes/implement-shift-frequency-46d4ea16d8be2f58.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + The :py:class:`~qiskit.pulse.instructions.ShiftFrequency` instruction allows users + to shift the frequency from the set frequency. For example:: + + sched += ShiftFrequency(-340e6, DriveChannel(0)) + + In this example, all the pulses applied to ``DriveChannel(0)`` after the + ``ShiftFrequency`` command will have the envelope a frequency decremented by 340MHz. diff --git a/test/python/pulse/test_schedule.py b/test/python/pulse/test_schedule.py index 7f608c9e2989..f19b0e8ab78c 100644 --- a/test/python/pulse/test_schedule.py +++ b/test/python/pulse/test_schedule.py @@ -20,7 +20,7 @@ from qiskit.pulse import (Play, SamplePulse, ShiftPhase, Instruction, SetFrequency, Acquire, pulse_lib, Snapshot, Delay, Gaussian, Drag, GaussianSquare, Constant, - functional_pulse) + functional_pulse, ShiftFrequency) from qiskit.pulse.channels import (MemorySlot, RegisterSlot, DriveChannel, AcquireChannel, SnapshotChannel, MeasureChannel) from qiskit.pulse.commands import PersistentValue, PulseInstruction @@ -665,6 +665,7 @@ def test_filter_inst_types(self): sched = sched.insert(10, Play(lp0, self.config.drive(1))) sched = sched.insert(30, ShiftPhase(-1.57, self.config.drive(0))) sched = sched.insert(40, SetFrequency(8.0, self.config.drive(0))) + sched = sched.insert(50, ShiftFrequency(4.0e6, self.config.drive(0))) for i in range(2): sched = sched.insert(60, Acquire(5, self.config.acquire(i), MemorySlot(i))) sched = sched.insert(90, Play(lp0, self.config.drive(0))) @@ -686,22 +687,30 @@ def test_filter_inst_types(self): for _, inst in no_pulse_and_fc.instructions: self.assertFalse(isinstance(inst, (Play, ShiftPhase))) self.assertEqual(len(only_pulse_and_fc.instructions), 4) - self.assertEqual(len(no_pulse_and_fc.instructions), 3) + self.assertEqual(len(no_pulse_and_fc.instructions), 4) # test on ShiftPhase only_fc, no_fc = \ self._filter_and_test_consistency(sched, instruction_types={ShiftPhase}) self.assertEqual(len(only_fc.instructions), 1) - self.assertEqual(len(no_fc.instructions), 6) + self.assertEqual(len(no_fc.instructions), 7) # test on SetFrequency - only_sf, no_sf = \ - self._filter_and_test_consistency(sched, - instruction_types=[SetFrequency]) - for _, inst in only_sf.instructions: + only_setf, no_setf = self._filter_and_test_consistency( + sched, instruction_types=[SetFrequency]) + for _, inst in only_setf.instructions: self.assertTrue(isinstance(inst, SetFrequency)) - self.assertEqual(len(only_sf.instructions), 1) - self.assertEqual(len(no_sf.instructions), 6) + self.assertEqual(len(only_setf.instructions), 1) + self.assertEqual(len(no_setf.instructions), 7) + + # test on ShiftFrequency + only_shiftf, no_shiftf = \ + self._filter_and_test_consistency(sched, + instruction_types=[ShiftFrequency]) + for _, inst in only_shiftf.instructions: + self.assertTrue(isinstance(inst, ShiftFrequency)) + self.assertEqual(len(only_shiftf.instructions), 1) + self.assertEqual(len(no_shiftf.instructions), 7) def test_filter_intervals(self): """Test filtering on intervals.""" diff --git a/test/python/qobj/test_pulse_converter.py b/test/python/qobj/test_pulse_converter.py index d8f082c95e34..c846710bb796 100644 --- a/test/python/qobj/test_pulse_converter.py +++ b/test/python/qobj/test_pulse_converter.py @@ -23,7 +23,7 @@ LoConfigConverter) from qiskit.pulse.commands import (SamplePulse, FrameChange, PersistentValue, Snapshot, Acquire, Gaussian, GaussianSquare, Constant, Drag) -from qiskit.pulse.instructions import ShiftPhase, SetFrequency, Play, Delay +from qiskit.pulse.instructions import ShiftPhase, SetFrequency, Play, Delay, ShiftFrequency from qiskit.pulse.channels import (DriveChannel, ControlChannel, MeasureChannel, AcquireChannel, MemorySlot, RegisterSlot) from qiskit.pulse.schedule import ParameterizedSchedule, Schedule @@ -157,6 +157,20 @@ def test_set_frequency(self): self.assertEqual(converter(0, instruction), valid_qobj) + def test_shift_frequency(self): + """Test converted qobj from ShiftFrequency.""" + converter = InstructionToQobjConverter(PulseQobjInstruction, meas_level=2) + instruction = ShiftFrequency(8.0e9, DriveChannel(0)) + + valid_qobj = PulseQobjInstruction( + name='shiftf', + ch='d0', + t0=0, + frequency=8.0 + ) + + self.assertEqual(converter(0, instruction), valid_qobj) + def test_persistent_value(self): """Test converted qobj from PersistentValueInstruction.""" converter = InstructionToQobjConverter(PulseQobjInstruction, meas_level=2) @@ -301,7 +315,7 @@ def test_frame_change(self): self.assertEqual(converted_instruction.instructions[0][-1], instruction) def test_set_frequency(self): - """Test converted qobj from FrameChangeInstruction.""" + """Test converted qobj from SetFrequency.""" instruction = SetFrequency(8.0e9, DriveChannel(0)) qobj = PulseQobjInstruction(name='setf', ch='d0', t0=0, frequency=8.0) @@ -312,6 +326,18 @@ def test_set_frequency(self): self.assertEqual(converted_instruction.instructions[0][-1], instruction) self.assertTrue('frequency' in qobj.to_dict()) + def test_shift_frequency(self): + """Test converted qobj from ShiftFrequency.""" + instruction = ShiftFrequency(8.0e9, DriveChannel(0)) + + qobj = PulseQobjInstruction(name='shiftf', ch='d0', t0=0, frequency=8.0) + converted_instruction = self.converter(qobj) + + self.assertEqual(converted_instruction.start_time, 0) + self.assertEqual(converted_instruction.duration, 0) + self.assertEqual(converted_instruction.instructions[0][-1], instruction) + self.assertTrue('frequency' in qobj.to_dict()) + def test_delay(self): """Test converted qobj from Delay.""" instruction = Delay(10, DriveChannel(0)) diff --git a/test/python/qobj/test_qobj.py b/test/python/qobj/test_qobj.py index b1b0622e64d8..24659dce6b5d 100644 --- a/test/python/qobj/test_qobj.py +++ b/test/python/qobj/test_qobj.py @@ -232,6 +232,7 @@ def setUp(self): PulseQobjInstruction(name='pv', t0=10, ch='d0', val=0.1 + 0.0j), PulseQobjInstruction(name='pv', t0=10, ch='d0', val='P1'), PulseQobjInstruction(name='setf', t0=10, ch='d0', frequency=8.0), + PulseQobjInstruction(name='shiftf', t0=10, ch='d0', frequency=4.0), PulseQobjInstruction(name='acquire', t0=15, duration=5, qubits=[0], memory_slot=[0], kernels=[ @@ -266,6 +267,7 @@ def setUp(self): {'name': 'pv', 't0': 10, 'ch': 'd0', 'val': 0.1+0j}, {'name': 'pv', 't0': 10, 'ch': 'd0', 'val': 'P1'}, {'name': 'setf', 't0': 10, 'ch': 'd0', 'frequency': 8.0}, + {'name': 'shiftf', 't0': 10, 'ch': 'd0', 'frequency': 4.0}, {'name': 'acquire', 't0': 15, 'duration': 5, 'qubits': [0], 'memory_slot': [0], 'kernels': [{'name': 'boxcar', diff --git a/test/python/visualization/test_pulse_visualization_output.py b/test/python/visualization/test_pulse_visualization_output.py index 0a91143c030e..24761c7bb3c5 100644 --- a/test/python/visualization/test_pulse_visualization_output.py +++ b/test/python/visualization/test_pulse_visualization_output.py @@ -23,7 +23,7 @@ from qiskit.pulse.channels import (DriveChannel, MeasureChannel, ControlChannel, AcquireChannel, MemorySlot, RegisterSlot) from qiskit.pulse.commands import FrameChange -from qiskit.pulse.instructions import SetFrequency, Play, Acquire, Delay, Snapshot +from qiskit.pulse.instructions import SetFrequency, Play, Acquire, Delay, Snapshot, ShiftFrequency from qiskit.pulse.schedule import Schedule from qiskit.tools.visualization import HAS_MATPLOTLIB from qiskit.visualization import pulse_drawer @@ -68,6 +68,7 @@ def sample_schedule(self): ControlChannel(0))) sched = sched.insert(60, FrameChange(phase=-1.57)(DriveChannel(0))) sched = sched.insert(60, SetFrequency(8.0, DriveChannel(0))) + sched = sched.insert(70, ShiftFrequency(4.0e6, DriveChannel(0))) sched = sched.insert(30, gp1(DriveChannel(1))) sched = sched.insert(60, gp0(ControlChannel(0))) sched = sched.insert(60, gs0(MeasureChannel(0)))