diff --git a/qiskit/pulse/__init__.py b/qiskit/pulse/__init__.py index 8feb8e203d4..e0074e68627 100644 --- a/qiskit/pulse/__init__.py +++ b/qiskit/pulse/__init__.py @@ -47,6 +47,8 @@ Delay Play SetFrequency + ShiftFrequency + SetPhase ShiftPhase Snapshot @@ -138,7 +140,7 @@ from .exceptions import PulseError from .instruction_schedule_map import InstructionScheduleMap from .instructions import (Acquire, Instruction, Delay, Play, ShiftPhase, Snapshot, - SetFrequency, ShiftFrequency) + SetFrequency, ShiftFrequency, SetPhase) 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 e26d7269f3e..dd97a77d65e 100644 --- a/qiskit/pulse/instructions/__init__.py +++ b/qiskit/pulse/instructions/__init__.py @@ -40,6 +40,7 @@ Play SetFrequency ShiftFrequency + SetPhase ShiftPhase Snapshot @@ -55,6 +56,6 @@ from .delay import Delay from .instruction import Instruction from .frequency import SetFrequency, ShiftFrequency -from .phase import ShiftPhase +from .phase import ShiftPhase, SetPhase from .play import Play from .snapshot import Snapshot diff --git a/qiskit/pulse/instructions/phase.py b/qiskit/pulse/instructions/phase.py index cf37132cef6..a0d9b48df8a 100644 --- a/qiskit/pulse/instructions/phase.py +++ b/qiskit/pulse/instructions/phase.py @@ -12,7 +12,11 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""The shift phase instruction updates the modulation phase of pulses played on a channel.""" +"""The phase instructions update the modulation phase of pulses played on a channel. +This includes ``SetPhase`` instructions which lock the modulation to a particular phase +at that moment, and ``ShiftPhase`` instructions which increase the existing phase by a +relative amount. +""" import warnings @@ -89,3 +93,45 @@ def __call__(self, channel: PulseChannel) -> 'ShiftPhase': "example, ShiftPhase(3.14)(DriveChannel(0)) should be replaced by " "ShiftPhase(3.14, DriveChannel(0)).", DeprecationWarning) return ShiftPhase(self.phase, channel) + + +class SetPhase(Instruction): + r"""The set phase instruction sets the phase of the proceeding pulses on that channel + to ``phase`` radians. + + In particular, a PulseChannel creates pulses of the form + + .. math:: + + Re[\exp(i 2\pi f jdt + \phi) d_j] + + The ``SetPhase`` instruction sets :math:`\phi` to the instruction's ``phase`` operand. + """ + + def __init__(self, + phase: float, + channel: PulseChannel, + name: Optional[str] = None): + """Instantiate a set phase instruction, setting the output signal phase on ``channel`` + to ``phase`` [radians]. + + Args: + phase: The rotation angle in radians. + channel: The channel this instruction operates on. + name: Display name for this instruction. + """ + self._phase = phase + self._channel = channel + super().__init__((phase, channel), 0, (channel,), name=name) + + @property + def phase(self) -> float: + """Return the rotation angle enacted by this instruction in radians.""" + return self._phase + + @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 aad3aebd7ca..ffed067a199 100644 --- a/qiskit/qobj/converters/pulse_instruction.py +++ b/qiskit/qobj/converters/pulse_instruction.py @@ -298,6 +298,24 @@ def convert_shift_frequency(self, shift, instruction): } return self._qobj_model(**command_dict) + @bind_instruction(instructions.SetPhase) + def convert_set_phase(self, shift, instruction): + """Return converted `SetPhase`. + + Args: + shift(int): Offset time. + instruction (SetPhase): Set phase instruction. + Returns: + dict: Dictionary of required parameters. + """ + command_dict = { + 'name': 'setp', + 't0': shift + instruction.start_time, + 'ch': instruction.channel.name, + 'phase': instruction.phase + } + return self._qobj_model(**command_dict) + @bind_instruction(instructions.ShiftPhase) def convert_shift_phase(self, shift, instruction): """Return converted `ShiftPhase`. @@ -520,6 +538,32 @@ def convert_acquire(self, instruction): return schedule + @bind_name('setp') + def convert_set_phase(self, instruction): + """Return converted `SetPhase`. + + Args: + instruction (PulseQobjInstruction): phase set qobj instruction + Returns: + Schedule: Converted and scheduled Instruction + """ + t0 = instruction.t0 + channel = self.get_channel(instruction.ch) + phase = instruction.phase + + # This is parameterized + if isinstance(phase, str): + phase_expr = parse_string_expr(phase, partial_binding=False) + + def gen_fc_sched(*args, **kwargs): + # this should be real value + _phase = phase_expr(*args, **kwargs) + return instructions.SetPhase(_phase, channel) << t0 + + return ParameterizedSchedule(gen_fc_sched, parameters=phase_expr.params) + + return instructions.SetPhase(phase, channel) << t0 + @bind_name('fc') def convert_shift_phase(self, instruction): """Return converted `ShiftPhase`. diff --git a/qiskit/schemas/examples/qobj_openpulse_example.json b/qiskit/schemas/examples/qobj_openpulse_example.json index 8c16d4cdba8..3cf7ef1b644 100644 --- a/qiskit/schemas/examples/qobj_openpulse_example.json +++ b/qiskit/schemas/examples/qobj_openpulse_example.json @@ -29,10 +29,11 @@ "instructions": [ {"name": "pulse1", "ch": "d0", "t0": 0}, {"name": "pv", "ch": "d0", "t0": 0, "val": [1,2]}, - {"name": "fc", "ch": "u0", "t0": 10, "phase": -0.1}, + {"name": "setp", "ch": "u0", "t0": 10, "phase": 3.14}, {"name": "setf", "ch": "d0", "t0": 10, "frequency": 8.0}, {"name": "acquire", "t0": 10, "duration": 90, "qubits": [0,1,2], "register_slot": [0,1,2], "memory_slot": [0,1,2]}, {"name": "shiftf", "ch": "d0", "t0": 20, "frequency": 4.0}, + {"name": "shiftp", "ch": "u0", "t0": 20, "phase": -0.1}, {"name": "copy", "register_orig": 1, "register_copy": [3,4]}, {"name": "bfunc", "mask": "0xE", "relation": "==", "val": "0xA", "register": 3, "memory": 3}, {"name": "pulse1", "ch": "d0", "t0": 120, "conditional": 3}, diff --git a/qiskit/schemas/qobj_schema.json b/qiskit/schemas/qobj_schema.json index ed611a8a002..a11f034a885 100644 --- a/qiskit/schemas/qobj_schema.json +++ b/qiskit/schemas/qobj_schema.json @@ -96,6 +96,8 @@ "enum": [ "pv", "fc", + "setp", + "shiftp", "setf", "shiftf", "acquire", @@ -156,6 +158,42 @@ ], "title": "Frame change pulse" }, + { + "properties": { + "name": { + "enum": [ + "setp" + ] + }, + "phase": { + "type": "number" + } + }, + "required": [ + "phase", + "t0", + "ch" + ], + "title": "Set Phase" + }, + { + "properties": { + "name": { + "enum": [ + "shiftp" + ] + }, + "phase": { + "type": "number" + } + }, + "required": [ + "phase", + "t0", + "ch" + ], + "title": "Shift Phase" + }, { "properties": { "name": { diff --git a/qiskit/visualization/pulse/matplotlib.py b/qiskit/visualization/pulse/matplotlib.py index 9da8325f81e..26bcfeb15cb 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, ShiftFrequency) + Instruction, ScheduleComponent, ShiftFrequency, SetPhase) class EventsOutputChannels: @@ -57,6 +57,7 @@ def __init__(self, t0: int, tf: int): self._waveform = None self._framechanges = None + self._setphase = None self._frequencychanges = None self._conditionals = None self._snapshots = None @@ -97,6 +98,14 @@ def framechanges(self) -> Dict[int, FrameChangeInstruction]: return self._trim(self._framechanges) + @property + def setphase(self) -> Dict[int, SetPhase]: + """Get the SetPhase phase values.""" + if self._setphase is None: + self._build_waveform() + + return self._trim(self._setphase) + @property def frequencychanges(self) -> Dict[int, SetFrequency]: """Get the frequency changes.""" @@ -143,7 +152,8 @@ def is_empty(self) -> bool: Returns: bool: if the channel has nothing to plot """ - if any(self.waveform) or self.framechanges or self.conditionals or self.snapshots: + if (any(self.waveform) or self.framechanges or self.setphase or + self.conditionals or self.snapshots): return False return True @@ -160,12 +170,16 @@ def to_table(self, name: str) -> List[Tuple[int, str, str]]: time_event = [] framechanges = self.framechanges + setphase = self.setphase conditionals = self.conditionals snapshots = self.snapshots frequencychanges = self.frequencychanges for key, val in framechanges.items(): - data_str = 'framechange: %.2f' % val + data_str = 'shift phase: %.2f' % val + time_event.append((key, name, data_str)) + for key, val in setphase.items(): + data_str = 'set phase: %.2f' % val time_event.append((key, name, data_str)) for key, val in conditionals.items(): data_str = 'conditional, %s' % val @@ -183,6 +197,7 @@ def _build_waveform(self): """Create waveform from stored pulses. """ self._framechanges = {} + self._setphase = {} self._frequencychanges = {} self._conditionals = {} self._snapshots = {} @@ -195,11 +210,15 @@ def _build_waveform(self): if time > self.tf: break tmp_fc = 0 + tmp_set_phase = 0 tmp_sf = None for command in commands: if isinstance(command, (FrameChange, ShiftPhase)): tmp_fc += command.phase pv[time:] = 0 + elif isinstance(command, SetPhase): + tmp_set_phase = command.phase + pv[time:] = 0 elif isinstance(command, SetFrequency): tmp_sf = command.frequency elif isinstance(command, ShiftFrequency): @@ -209,6 +228,9 @@ def _build_waveform(self): if tmp_fc != 0: self._framechanges[time] = tmp_fc fc += tmp_fc + if tmp_set_phase != 0: + self._setphase[time] = tmp_set_phase + fc = tmp_set_phase if tmp_sf is not None: self._frequencychanges[time] = tmp_sf for command in commands: @@ -370,7 +392,7 @@ def _build_channels(self, schedule: ScheduleComponent, # take channels that do not only contain framechanges else: for start_time, instruction in schedule.instructions: - if not isinstance(instruction, (FrameChangeInstruction, ShiftPhase)): + if not isinstance(instruction, (FrameChangeInstruction, ShiftPhase, SetPhase)): _channels.update(instruction.channels) _channels.update(channels) diff --git a/releasenotes/notes/implement-set-phase-b5581ad6085b3cec.yaml b/releasenotes/notes/implement-set-phase-b5581ad6085b3cec.yaml new file mode 100644 index 00000000000..9165ce05fd3 --- /dev/null +++ b/releasenotes/notes/implement-set-phase-b5581ad6085b3cec.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Added Phase instruction - :py:class:`~qiskit.pulse.SetPhase`. It sets the phase of the + proceeding pulses to ``phase`` radians. For example:: + + sched += SetPhase(3.14, DriveChannel(0)) + + In this example, the phase of the pulses applied to ``DriveChannel(0)`` after the + ``SetPhase`` instruction will be set to 3.14 radians. diff --git a/test/python/pulse/test_schedule.py b/test/python/pulse/test_schedule.py index fdd5ca04467..a454d75008f 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, ShiftFrequency) + functional_pulse, ShiftFrequency, SetPhase) from qiskit.pulse.channels import (MemorySlot, RegisterSlot, DriveChannel, AcquireChannel, SnapshotChannel, MeasureChannel) from qiskit.pulse.commands import PersistentValue, PulseInstruction @@ -631,6 +631,7 @@ def test_filter_inst_types(self): 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))) + sched = sched.insert(55, SetPhase(3.14, 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))) @@ -652,13 +653,19 @@ 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), 4) + self.assertEqual(len(no_pulse_and_fc.instructions), 5) # 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), 7) + self.assertEqual(len(no_fc.instructions), 8) + + # test on SetPhase + only_setp, no_setp = \ + self._filter_and_test_consistency(sched, instruction_types={SetPhase}) + self.assertEqual(len(only_setp.instructions), 1) + self.assertEqual(len(no_setp.instructions), 8) # test on SetFrequency only_setf, no_setf = self._filter_and_test_consistency( @@ -666,7 +673,7 @@ def test_filter_inst_types(self): for _, inst in only_setf.instructions: self.assertTrue(isinstance(inst, SetFrequency)) self.assertEqual(len(only_setf.instructions), 1) - self.assertEqual(len(no_setf.instructions), 7) + self.assertEqual(len(no_setf.instructions), 8) # test on ShiftFrequency only_shiftf, no_shiftf = \ @@ -675,7 +682,7 @@ def test_filter_inst_types(self): 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) + self.assertEqual(len(no_shiftf.instructions), 8) 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 c846710bb79..e1d91887fa7 100644 --- a/test/python/qobj/test_pulse_converter.py +++ b/test/python/qobj/test_pulse_converter.py @@ -23,7 +23,8 @@ LoConfigConverter) from qiskit.pulse.commands import (SamplePulse, FrameChange, PersistentValue, Snapshot, Acquire, Gaussian, GaussianSquare, Constant, Drag) -from qiskit.pulse.instructions import ShiftPhase, SetFrequency, Play, Delay, ShiftFrequency +from qiskit.pulse.instructions import (SetPhase, ShiftPhase, SetFrequency, ShiftFrequency, Play, + Delay) from qiskit.pulse.channels import (DriveChannel, ControlChannel, MeasureChannel, AcquireChannel, MemorySlot, RegisterSlot) from qiskit.pulse.schedule import ParameterizedSchedule, Schedule @@ -143,6 +144,20 @@ def test_frame_change(self): instruction = ShiftPhase(0.1, DriveChannel(0)) self.assertEqual(converter(0, instruction), valid_qobj) + def test_set_phase(self): + """Test converted qobj from FrameChangeInstruction.""" + converter = InstructionToQobjConverter(PulseQobjInstruction, meas_level=2) + instruction = SetPhase(3.14, DriveChannel(0)) + + valid_qobj = PulseQobjInstruction( + name='setp', + ch='d0', + t0=0, + phase=3.14 + ) + + self.assertEqual(converter(0, instruction), valid_qobj) + def test_set_frequency(self): """Test converted qobj from SetFrequency.""" converter = InstructionToQobjConverter(PulseQobjInstruction, meas_level=2) @@ -314,6 +329,16 @@ def test_frame_change(self): self.assertEqual(converted_instruction.duration, 0) self.assertEqual(converted_instruction.instructions[0][-1], instruction) + def test_set_phase(self): + """Test converted qobj from SetPhase.""" + qobj = PulseQobjInstruction(name='setp', ch='m0', t0=0, phase=3.14) + converted_instruction = self.converter(qobj) + + instruction = SetPhase(3.14, MeasureChannel(0)) + self.assertEqual(converted_instruction.start_time, 0) + self.assertEqual(converted_instruction.duration, 0) + self.assertEqual(converted_instruction.instructions[0][-1], instruction) + def test_set_frequency(self): """Test converted qobj from SetFrequency.""" instruction = SetFrequency(8.0e9, DriveChannel(0)) diff --git a/test/python/qobj/test_qobj.py b/test/python/qobj/test_qobj.py index 24659dce6b5..73f99da8627 100644 --- a/test/python/qobj/test_qobj.py +++ b/test/python/qobj/test_qobj.py @@ -231,6 +231,7 @@ def setUp(self): PulseQobjInstruction(name='fc', t0=5, ch='d0', phase='P1'), PulseQobjInstruction(name='pv', t0=10, ch='d0', val=0.1 + 0.0j), PulseQobjInstruction(name='pv', t0=10, ch='d0', val='P1'), + PulseQobjInstruction(name='setp', t0=10, ch='d0', phase=3.14), 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, @@ -266,6 +267,7 @@ def setUp(self): {'name': 'fc', 't0': 5, 'ch': 'd0', 'phase': 'P1'}, {'name': 'pv', 't0': 10, 'ch': 'd0', 'val': 0.1+0j}, {'name': 'pv', 't0': 10, 'ch': 'd0', 'val': 'P1'}, + {'name': 'setp', 't0': 10, 'ch': 'd0', 'phase': 3.14}, {'name': 'setf', 't0': 10, 'ch': 'd0', 'frequency': 8.0}, {'name': 'shiftf', 't0': 10, 'ch': 'd0', 'frequency': 4.0}, {'name': 'acquire', 't0': 15, 'duration': 5, diff --git a/test/python/visualization/test_pulse_visualization_output.py b/test/python/visualization/test_pulse_visualization_output.py index 24761c7bb3c..550e7bf10b7 100644 --- a/test/python/visualization/test_pulse_visualization_output.py +++ b/test/python/visualization/test_pulse_visualization_output.py @@ -23,7 +23,8 @@ 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, ShiftFrequency +from qiskit.pulse.instructions import (SetFrequency, Play, Acquire, Delay, Snapshot, ShiftFrequency, + SetPhase, ShiftPhase) from qiskit.pulse.schedule import Schedule from qiskit.tools.visualization import HAS_MATPLOTLIB from qiskit.visualization import pulse_drawer @@ -59,7 +60,6 @@ def sample_schedule(self): gp1 = pulse_lib.gaussian(duration=20, amp=-1.0, sigma=2.0) gs0 = pulse_lib.gaussian_square(duration=20, amp=-1.0, sigma=2.0, risefall=3) - fc_pi_2 = FrameChange(phase=1.57) acquire = Acquire(10) delay = Delay(100) sched = Schedule(name='test_schedule') @@ -68,11 +68,12 @@ 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(60, SetPhase(3.14, 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))) - sched = sched.insert(90, fc_pi_2(DriveChannel(0))) + sched = sched.insert(90, ShiftPhase(1.57, DriveChannel(0))) sched = sched.insert(90, acquire(AcquireChannel(1), MemorySlot(1), RegisterSlot(1)))