Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pulse instruction and schedule equality testing #2873

Merged
merged 15 commits into from
Aug 14, 2019
Merged
Show file tree
Hide file tree
Changes from 11 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ The format is based on [Keep a Changelog].
- The `as_dict` method of Qobj is deprecated in favor of `to_dict`.

### Added
- Ability to check for equality of pulse `Schedule` and `Instruction`.
- Added tests for `gate_map` and reference images for testing `plot_gate_map`
- New `CountOpsLongest` analysis pass to retrieve the number of operations
on the longest path of the DAGCircuit.
Expand Down Expand Up @@ -59,6 +60,7 @@ The format is based on [Keep a Changelog].

### Changed

- `Schedule.instructions` now returns with time-ordering.
- The number of memory slots required will now be inferred from the supplied
schedules if `memory_slots` is not supplied.
- All circuit drawers now express most commonly used fractions
Expand Down
11 changes: 6 additions & 5 deletions qiskit/pulse/commands/acquire.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,12 @@ def __eq__(self, other: 'Acquire'):
Returns:
bool: are self and other equal.
"""
if type(self) is type(other) and \
self.kernel == other.kernel and \
self.discriminator == other.discriminator:
return True
return False
return (super().__eq__(other) and
self.kernel == other.kernel and
self.discriminator == other.discriminator)

def __hash__(self):
return hash((super().__hash__(), self.kernel, self.discriminator))

def __repr__(self):
return '%s(%s, duration=%d, kernel=%s, discriminator=%s)' % \
Expand Down
10 changes: 3 additions & 7 deletions qiskit/pulse/commands/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,11 @@ def __eq__(self, other: 'Command'):
Returns:
bool: are self and other equal
"""
if type(self) is type(other) and \
self._duration == other._duration and \
self._name == other._name:
return True
return False
return (type(self) is type(other)) and (self.duration == other.duration)

def __hash__(self):
return hash((type(self), self._duration, self._name))
return hash((type(self), self.duration, self.name))

def __repr__(self):
return '%s(name=%s, duration=%d)' % (self.__class__.__name__,
self._name, self._duration)
self.name, self.duration)
8 changes: 4 additions & 4 deletions qiskit/pulse/commands/frame_change.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ def __eq__(self, other: 'FrameChange'):
Returns:
bool: are self and other equal
"""
if type(self) is type(other) and \
self.phase == other.phase:
return True
return False
return super().__eq__(other) and (self.phase == other.phase)

def __hash__(self):
return hash((super().__hash__(), self.phase))

def __repr__(self):
return '%s(%s, phase=%.3f)' % (self.__class__.__name__, self.name, self.phase)
Expand Down
22 changes: 16 additions & 6 deletions qiskit/pulse/commands/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,13 +241,23 @@ def draw(self, dt: float = 1, style: Optional['SchedStyle'] = None,
interactive=interactive, table=table,
label=label, framechange=framechange)

def __add__(self, schedule: ScheduleComponent) -> 'Schedule':
"""Return a new schedule with `schedule` inserted within `self` at `start_time`."""
return self.append(schedule)
def __eq__(self, other: 'Instruction'):
"""Check if this Instruction is equal to the `other` instruction.

def __or__(self, schedule: ScheduleComponent) -> 'Schedule':
"""Return a new schedule which is the union of `self` and `schedule`."""
return self.union(schedule)
Equality is determined by the instruction sharing the same command and channels.
"""
return (self.command == other.command) and (set(self.channels) == set(other.channels))

def __hash__(self):
return hash((self.command.__hash__(), self.channels.__hash__()))

def __add__(self, other: ScheduleComponent) -> 'Schedule':
"""Return a new schedule with `other` inserted within `self` at `start_time`."""
return self.append(other)

def __or__(self, other: ScheduleComponent) -> 'Schedule':
"""Return a new schedule which is the union of `self` and `other`."""
return self.union(other)

def __lshift__(self, time: int) -> 'Schedule':
"""Return a new schedule which is shifted forward by `time`."""
Expand Down
13 changes: 7 additions & 6 deletions qiskit/pulse/commands/meas_opts.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,15 @@ def __eq__(self, other: 'MeasOpts'):
Returns:
bool: are self and other equal
"""
if type(self) is type(other) and \
self._name == other._name and \
self._params == other._params:
return True
return False
return (type(self) is type(other) and
self.name == other.name and
self.params == other.params)

def __hash__(self):
return hash((super().__hash__(), self.name, self.params))

def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self._name)
return '%s(%s)' % (self.__class__.__name__, self.name)


class Discriminator(MeasOpts):
Expand Down
10 changes: 5 additions & 5 deletions qiskit/pulse/commands/persistent_value.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def value(self):
"""Persistent value amplitude."""
return self._value

def __eq__(self, other: 'PersistentValue'):
def __eq__(self, other: 'PersistentValue') -> bool:
"""Two PersistentValues are the same if they are of the same type
and have the same value.

Expand All @@ -61,10 +61,10 @@ def __eq__(self, other: 'PersistentValue'):
Returns:
bool: are self and other equal
"""
if type(self) is type(other) and \
self.value == other.value:
return True
return False
return super().__eq__(other) and self.value == other.value

def __hash__(self):
return hash((super().__hash__(), self.value))

def __repr__(self):
return '%s(%s, value=%s)' % (self.__class__.__name__, self.name, self.value)
Expand Down
7 changes: 2 additions & 5 deletions qiskit/pulse/commands/sample_pulse.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,10 @@ def __eq__(self, other: 'SamplePulse'):
Returns:
bool: are self and other equal
"""
if super().__eq__(other) and \
(self._samples == other._samples).all():
return True
return False
return super().__eq__(other) and (self.samples == other.samples).all()

def __hash__(self):
return hash((super().__hash__(), self._samples.tostring()))
return hash((super().__hash__(), self.samples.tostring()))
taalexander marked this conversation as resolved.
Show resolved Hide resolved

def __repr__(self):
return '%s(%s, duration=%d)' % (self.__class__.__name__, self.name, self.duration)
Expand Down
9 changes: 5 additions & 4 deletions qiskit/pulse/commands/snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,12 @@ def __eq__(self, other: 'Snapshot'):
Returns:
bool: are self and other equal
"""
if (type(self) is type(other) and
return (super().__eq__(other) and
self.label == other.label and
self.type == other.type):
return True
return False
self.type == other.type)

def __hash__(self):
return hash((super().__hash__(), self.label, self.type))

# pylint: disable=arguments-differ
def to_instruction(self):
Expand Down
58 changes: 49 additions & 9 deletions qiskit/pulse/schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,15 @@ def _children(self) -> Tuple[Tuple[int, ScheduleComponent], ...]:
return self.__children

@property
def instructions(self) -> Tuple[Tuple[int, 'Instruction']]:
"""Iterable for getting instructions from Schedule tree."""
return tuple(self._instructions())
def instructions(self) -> Tuple[Tuple[int, 'Instruction'], ...]:
"""Get time-ordered instructions from Schedule tree."""

def key(time_inst_pair):
inst = time_inst_pair[1]
return (time_inst_pair[0], inst.duration,
min(chan.index for chan in inst.channels))

return tuple(sorted(self._instructions(), key=key))

def ch_duration(self, *channels: List[Channel]) -> int:
"""Return duration of schedule over supplied channels.
Expand Down Expand Up @@ -322,13 +328,47 @@ def draw(self, dt: float = 1, style: Optional['SchedStyle'] = None,
interactive=interactive, table=table,
label=label, framechange=framechange)

def __add__(self, schedule: ScheduleComponent) -> 'Schedule':
"""Return a new schedule with `schedule` inserted within `self` at `start_time`."""
return self.append(schedule)
def __eq__(self, other: ScheduleComponent) -> bool:
"""Test if two ScheduleComponents are equal.

Equality is checked by verifying there is an equal instruction at every time
in `other` for every instruction in this Schedule.

Warning: This does not check for logical equivalencly. Ie.,
```python
>>> (Delay(10)(DriveChannel(0)) + Delay(10)(DriveChannel(0)) ==
Delay(20)(DriveChannel(0)))
False
```
"""
channels = set(self.channels)
other_channels = set(other.channels)

# first check channels are the same
if channels != other_channels:
return False

# then verify same number of instructions in each
instructions = self.instructions
other_instructions = other.instructions
if len(instructions) != len(other_instructions):
return False

# finally check each instruction in `other` is in this schedule
for idx, inst in enumerate(other_instructions):
# check assumes `Schedule.instructions` is sorted consistently
if instructions[idx] != inst:
return False

return True

def __add__(self, other: ScheduleComponent) -> 'Schedule':
"""Return a new schedule with `other` inserted within `self` at `start_time`."""
return self.append(other)

def __or__(self, schedule: ScheduleComponent) -> 'Schedule':
"""Return a new schedule which is the union of `self` and `schedule`."""
return self.union(schedule)
def __or__(self, other: ScheduleComponent) -> 'Schedule':
"""Return a new schedule which is the union of `self` and `other`."""
return self.union(other)

def __lshift__(self, time: int) -> 'Schedule':
"""Return a new schedule which is shifted forward by `time`."""
Expand Down
41 changes: 41 additions & 0 deletions test/python/pulse/test_schedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,47 @@ def my_test_par_sched_two(x, y, z):
self.assertEqual(cmd_def.get_parameters('test', 0), ('x', 'y', 'z'))


class TestScheduleEquality(QiskitTestCase):
"""Test equality of schedules."""

def test_different_channels(self):
"""Test equality is False if different channels."""
self.assertNotEqual(Schedule(FrameChange(0)(DriveChannel(0))),
Schedule(FrameChange(0)(DriveChannel(1))))

def test_same_time_equal(self):
"""Test equal if instruction at same time."""

self.assertEqual(Schedule((0, FrameChange(0)(DriveChannel(1)))),
Schedule((0, FrameChange(0)(DriveChannel(1)))))

def test_different_time_not_equal(self):
"""Test that not equal if instruction at different time."""
self.assertNotEqual(Schedule((0, FrameChange(0)(DriveChannel(1)))),
Schedule((1, FrameChange(0)(DriveChannel(1)))))

def test_single_channel_out_of_order(self):
"""Test that schedule with single channel equal when out of order."""
instructions = [(0, FrameChange(0)(DriveChannel(0))),
(15, SamplePulse(np.ones(10))(DriveChannel(0))),
(5, SamplePulse(np.ones(10))(DriveChannel(0)))]

self.assertEqual(Schedule(*instructions), Schedule(*reversed(instructions)))

def test_multiple_channels_out_of_order(self):
"""Test that schedule with multiple channels equal when out of order."""
instructions = [(0, FrameChange(0)(DriveChannel(1))),
(1, Acquire(10)(AcquireChannel(0), MemorySlot(1)))]

self.assertEqual(Schedule(*instructions), Schedule(*reversed(instructions)))

def test_different_name_equal(self):
"""Test that names are ignored when checking equality."""

self.assertEqual(Schedule((0, FrameChange(0, name='fc1')(DriveChannel(1))), name='s1'),
Schedule((0, FrameChange(0, name='fc2')(DriveChannel(1))), name='s2'))


class TestScheduleWithDeviceSpecification(QiskitTestCase):
"""Schedule tests."""
# TODO: This test will be deprecated in future update.
Expand Down