Skip to content

Commit

Permalink
Add SetPhase instruction to pulse (Qiskit#4540)
Browse files Browse the repository at this point in the history
* Add SetPhase instruction

* visualization

* Update qobj schema

* Review suggestions

* typo

* Review comments

* change framechange to phase in mpl draw

* typo

* add reno

* Separate set phase and shift phase in visualization

* lint

* lint

* review comments

* review comments

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
SooluThomas and mergify[bot] authored Jun 12, 2020
1 parent 2a7d022 commit 64a73a3
Show file tree
Hide file tree
Showing 12 changed files with 216 additions and 17 deletions.
4 changes: 3 additions & 1 deletion qiskit/pulse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
Delay
Play
SetFrequency
ShiftFrequency
SetPhase
ShiftPhase
Snapshot
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion qiskit/pulse/instructions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
Play
SetFrequency
ShiftFrequency
SetPhase
ShiftPhase
Snapshot
Expand All @@ -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
48 changes: 47 additions & 1 deletion qiskit/pulse/instructions/phase.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
44 changes: 44 additions & 0 deletions qiskit/qobj/converters/pulse_instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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`.
Expand Down
3 changes: 2 additions & 1 deletion qiskit/schemas/examples/qobj_openpulse_example.json
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
38 changes: 38 additions & 0 deletions qiskit/schemas/qobj_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@
"enum": [
"pv",
"fc",
"setp",
"shiftp",
"setf",
"shiftf",
"acquire",
Expand Down Expand Up @@ -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": {
Expand Down
30 changes: 26 additions & 4 deletions qiskit/visualization/pulse/matplotlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -183,6 +197,7 @@ def _build_waveform(self):
"""Create waveform from stored pulses.
"""
self._framechanges = {}
self._setphase = {}
self._frequencychanges = {}
self._conditionals = {}
self._snapshots = {}
Expand All @@ -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):
Expand All @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions releasenotes/notes/implement-set-phase-b5581ad6085b3cec.yaml
Original file line number Diff line number Diff line change
@@ -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.
17 changes: 12 additions & 5 deletions test/python/pulse/test_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)))
Expand All @@ -652,21 +653,27 @@ 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(
sched, instruction_types=[SetFrequency])
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 = \
Expand All @@ -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."""
Expand Down
Loading

0 comments on commit 64a73a3

Please sign in to comment.