Skip to content

Commit

Permalink
Qiskit#2262 | Acquire commands on a single qubit (Qiskit#3574)
Browse files Browse the repository at this point in the history
* Acquire: AcquireInstruction change to sigle qbit, mem_slot, reg_slot

* Acquire: change usage of AcquireInstruction

* Tests: partial test fixing

* Assembler: tweak scheduler assembler to laverage new acquire + tests fixes

* Scheduler: fix scheduler to use acquire on single qubit + tests fix

* Style: fix lint errors

* Acquire: remove unecessary properties

* Style: fix lint errors

* Acuqire: back compatibility on acquire multiple qubits

* Acuqire: fix linter

* Acquire: refactor

* Acquire: fix implicit acquires; todo: revisit _validate_meas_map method

* Acquire: pylint fix

* Acquire: minor fixes

* Acquire: fix add implicits acquires function

* Acquire: instruction properties fix

* Acquire: remove test for validating meas map

* Linter: fix errors

* Acquire: style fix

* Acquire: remove deprecation in acquires property + fix positional arguments

* Acquire: back and forward compatibility

* Acquire: remove deprecation warning (hm, I thought I removed it before... weird ;) )

* Acquire: grammar fixes + split schedule acquire test

* Acquire: add release note

* Acquire: release notes remove issues

Co-authored-by: Lauren Capelluto <laurencapelluto@gmail.com>
  • Loading branch information
2 people authored and mergify[bot] committed Jan 28, 2020
1 parent 1e56f16 commit f158e2b
Show file tree
Hide file tree
Showing 12 changed files with 267 additions and 114 deletions.
19 changes: 14 additions & 5 deletions qiskit/assembler/assemble_schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def assemble_schedules(schedules, qobj_id, qobj_header, run_config):
# instructions
max_memory_slot = 0
qobj_instructions = []
acquire_instructions = []

# Instructions are returned as tuple of shifted time and instruction
for shift, instruction in schedule.instructions:
Expand Down Expand Up @@ -111,13 +112,16 @@ def assemble_schedules(schedules, qobj_id, qobj_header, run_config):
elif isinstance(instruction, AcquireInstruction):
max_memory_slot = max(max_memory_slot,
*[slot.index for slot in instruction.mem_slots])
if meas_map:
# verify all acquires satisfy meas_map
_validate_meas_map(instruction, meas_map)

acquire_instructions.append(instruction)

converted_instruction = instruction_converter(shift, instruction)
qobj_instructions.append(converted_instruction)

if meas_map:
# verify all acquires satisfy meas_map
_validate_meas_map(acquire_instructions, meas_map)

# memory slot size is memory slot index + 1 because index starts from zero
exp_memory_slot_size = max_memory_slot + 1
memory_slot_size = max(memory_slot_size, exp_memory_slot_size)
Expand Down Expand Up @@ -199,11 +203,16 @@ def assemble_schedules(schedules, qobj_id, qobj_header, run_config):
header=qobj_header)


def _validate_meas_map(acquire, meas_map):
def _validate_meas_map(instructions, meas_map):
"""Validate all qubits tied in meas_map are to be acquired."""
meas_map_set = [set(m) for m in meas_map]
# Verify that each qubit is listed once in measurement map
measured_qubits = {acq_ch.index for acq_ch in acquire.acquires}

acquires = []
for inst in instructions:
acquires += inst.acquires
measured_qubits = {acq_ch.index for acq_ch in acquires}

tied_qubits = set()
for meas_qubit in measured_qubits:
for map_inst in meas_map_set:
Expand Down
93 changes: 67 additions & 26 deletions qiskit/pulse/commands/acquire.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"""
Acquire.
"""
from typing import Union, List, Optional
import warnings
from typing import Optional, Union, List

from qiskit.pulse.channels import Qubit, MemorySlot, RegisterSlot, AcquireChannel
from qiskit.pulse.exceptions import PulseError
Expand Down Expand Up @@ -93,11 +94,15 @@ def __repr__(self):

# pylint: disable=arguments-differ
def to_instruction(self,
qubits: Union[Qubit, List[Qubit]],
mem_slots: Optional[Union[MemorySlot, List[MemorySlot]]] = None,
qubit: Union[AcquireChannel, List[AcquireChannel]],
mem_slot: Optional[Union[MemorySlot, List[MemorySlot]]] = None,
reg_slots: Optional[Union[RegisterSlot, List[RegisterSlot]]] = None,
mem_slots: Optional[Union[List[MemorySlot]]] = None,
reg_slot: Optional[RegisterSlot] = None,
name: Optional[str] = None) -> 'AcquireInstruction':
return AcquireInstruction(self, qubits, mem_slots, reg_slots, name=name)

return AcquireInstruction(self, qubit, mem_slot=mem_slot, reg_slot=reg_slot,
mem_slots=mem_slots, reg_slots=reg_slots, name=name)
# pylint: enable=arguments-differ


Expand All @@ -106,40 +111,72 @@ class AcquireInstruction(Instruction):

def __init__(self,
command: Acquire,
acquires: Union[AcquireChannel, List[AcquireChannel]],
mem_slots: Union[MemorySlot, List[MemorySlot]],
acquire: Union[AcquireChannel, List[AcquireChannel]],
mem_slot: Optional[Union[MemorySlot, List[MemorySlot]]] = None,
reg_slots: Optional[Union[RegisterSlot, List[RegisterSlot]]] = None,
mem_slots: Optional[Union[List[MemorySlot]]] = None,
reg_slot: Optional[RegisterSlot] = None,
name: Optional[str] = None):

if not isinstance(acquires, list):
acquires = [acquires]
if isinstance(acquire, list) or isinstance(mem_slot, list) or reg_slots:
warnings.warn('The AcquireInstruction no longer supports multiple qubits, '
'multiple memory slots and multiple reg slots. '
'This behavior has been deprecated. The parameter '
'"mem_slots" has been replaced by "mem_slot" and '
'"reg_slots" has been replaced by "reg_slot"', DeprecationWarning, 3)

if not isinstance(acquire, list):
acquire = [acquire]

if isinstance(acquires[0], Qubit):
if isinstance(acquire[0], Qubit):
raise PulseError("AcquireInstruction can not be instantiated with Qubits, "
"which are deprecated.")

if not (mem_slots or reg_slots):
if mem_slot and not isinstance(mem_slot, list):
mem_slot = [mem_slot]
elif mem_slots:
mem_slot = mem_slots

if reg_slot:
reg_slot = [reg_slot]
elif reg_slots and not isinstance(reg_slots, list):
reg_slot = [reg_slots]
else:
reg_slot = reg_slots

if not (mem_slot or reg_slot):
raise PulseError('Neither memoryslots or registers were supplied')

if mem_slots:
if isinstance(mem_slots, MemorySlot):
mem_slots = [mem_slots]
elif len(acquires) != len(mem_slots):
raise PulseError("#mem_slots must be equals to #acquires")

if reg_slots:
if isinstance(reg_slots, RegisterSlot):
reg_slots = [reg_slots]
if len(acquires) != len(reg_slots):
raise PulseError("#reg_slots must be equals to #acquires")
if mem_slot and len(acquire) != len(mem_slot):
raise PulseError("The number of mem_slots must be equals to the number of acquires")

if reg_slot:
if len(acquire) != len(reg_slot):
raise PulseError("The number of reg_slots must be equals "
"to the number of acquires")
else:
reg_slots = []
reg_slot = []

super().__init__(command, *acquires, *mem_slots, *reg_slots, name=name)
super().__init__(command, *acquire, *mem_slot, *reg_slot, name=name)

self._acquires = acquires
self._mem_slots = mem_slots
self._reg_slots = reg_slots
self._acquires = acquire
self._mem_slots = mem_slot
self._reg_slots = reg_slot

@property
def acquire(self):
"""Acquire channel to be acquired on."""
return self._acquires[0] if self._acquires else None

@property
def mem_slot(self):
"""MemorySlot."""
return self._mem_slots[0] if self._mem_slots else None

@property
def reg_slot(self):
"""RegisterSlot."""
return self._reg_slots[0] if self._reg_slots else None

@property
def acquires(self):
Expand All @@ -149,9 +186,13 @@ def acquires(self):
@property
def mem_slots(self):
"""MemorySlots."""
warnings.warn('"mem_slots" is deprecated and being replaced by "mem_slot"',
DeprecationWarning, 3)
return self._mem_slots

@property
def reg_slots(self):
"""RegisterSlots."""
warnings.warn('"reg_slots" is deprecated and being replaced by "reg_slot"',
DeprecationWarning, 3)
return self._reg_slots
2 changes: 1 addition & 1 deletion qiskit/pulse/commands/instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def __init__(self, command, *channels: List[Channel],
duration = command.duration

self._timeslots = TimeslotCollection(*(Timeslot(Interval(0, duration), channel)
for channel in channels))
for channel in channels if channel is not None))

channels = self.channels

Expand Down
14 changes: 10 additions & 4 deletions qiskit/pulse/reschedule.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,14 @@ def add_implicit_acquires(schedule: ScheduleComponent, meas_map: List[List[int]]
Schedule
"""
new_schedule = Schedule(name=schedule.name)
acquire_map = dict()

for time, inst in schedule.instructions:
if isinstance(inst, AcquireInstruction):
if any([acq.index != mem.index for acq, mem in zip(inst.acquires, inst.mem_slots)]):
warnings.warn("One of your acquires was mapped to a memory slot which didn't match"
" the qubit index. I'm relabeling them to match.")

cmd = Acquire(inst.duration, inst.command.discriminator, inst.command.kernel)
# Get the label of all qubits that are measured with the qubit(s) in this instruction
existing_qubits = {chan.index for chan in inst.acquires}
Expand All @@ -155,10 +157,14 @@ def add_implicit_acquires(schedule: ScheduleComponent, meas_map: List[List[int]]
all_qubits.extend(sublist)
# Replace the old acquire instruction by a new one explicitly acquiring all qubits in
# the measurement group.
new_schedule |= AcquireInstruction(
cmd,
[AcquireChannel(i) for i in all_qubits],
[MemorySlot(i) for i in all_qubits]) << time
for i in all_qubits:
explicit_inst = AcquireInstruction(cmd, AcquireChannel(i), MemorySlot(i)) << time
if time not in acquire_map:
new_schedule |= explicit_inst
acquire_map = {time: {i}}
elif i not in acquire_map[time]:
new_schedule |= explicit_inst
acquire_map[time].add(i)
else:
new_schedule |= inst << time

Expand Down
9 changes: 5 additions & 4 deletions qiskit/qobj/converters/pulse_instruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,15 +321,15 @@ def convert_acquire(self, instruction):
t0 = instruction.t0
duration = instruction.duration
qubits = instruction.qubits
qubit_channels = [channels.AcquireChannel(qubit) for qubit in qubits]
acquire_channels = [channels.AcquireChannel(qubit) for qubit in qubits]

mem_slots = [channels.MemorySlot(instruction.memory_slot[i]) for i in range(len(qubits))]

if hasattr(instruction, 'register_slot'):
register_slots = [channels.RegisterSlot(instruction.register_slot[i])
for i in range(len(qubits))]
else:
register_slots = None
register_slots = [None] * len(qubits)

discriminators = (instruction.discriminators
if hasattr(instruction, 'discriminators') else None)
Expand All @@ -356,8 +356,9 @@ def convert_acquire(self, instruction):

cmd = commands.Acquire(duration, discriminator=discriminator, kernel=kernel)
schedule = Schedule()
schedule |= commands.AcquireInstruction(cmd, qubit_channels, mem_slots,
register_slots) << t0

for acquire_channel, mem_slot, reg_slot in zip(acquire_channels, mem_slots, register_slots):
schedule |= commands.AcquireInstruction(cmd, acquire_channel, mem_slot, reg_slot) << t0

return schedule

Expand Down
13 changes: 6 additions & 7 deletions qiskit/scheduler/methods/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,16 +157,15 @@ def get_measure_schedule() -> CircuitPulseDef:
default_sched = inst_map.get('measure', qubits)
for time, inst in default_sched.instructions:
if isinstance(inst, AcquireInstruction):
mem_slots = []
for channel in inst.acquires:
if channel.index in qubit_mem_slots.keys():
mem_slots.append(MemorySlot(qubit_mem_slots[channel.index]))
mem_slot = MemorySlot(qubit_mem_slots[channel.index])
else:
mem_slots.append(MemorySlot(unused_mem_slots.pop()))
new_acquire = AcquireInstruction(command=inst.command,
acquires=inst.acquires,
mem_slots=mem_slots)
sched._union((time, new_acquire))
mem_slot = MemorySlot(unused_mem_slots.pop())
sched._union((time, AcquireInstruction(command=inst.command,
acquire=channel,
mem_slot=mem_slot)))

# Measurement pulses should only be added if its qubit was measured by the user
elif inst.channels[0].index in qubit_mem_slots.keys():
sched._union((time, inst))
Expand Down
20 changes: 20 additions & 0 deletions releasenotes/notes/acquire-single-channel-ea83cef8d991f945.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
prelude: >
Acquire and AcquireInstruction now can be applied to a single channel
features:
- |
Within the terra API allow acquires be applied to a single qubit.
This makes pulse programming more consistent and easier to reason
about as all operations in pulse would then be for single channels.
For example:
acquire = Acquire(duration=10)
schedule = Schedule()
schedule.insert(60, acquire(AcquireChannel(0), MemorySlot(0), RegisterSlot(0)))
schedule.insert(60, acquire(AcquireChannel(1), MemorySlot(1), RegisterSlot(1)))
deprecations:
- |
Acquire on multiple qubits has been deprecated.
``AcquireInstruction`` parameters ``mem_slots``, ``reg_slots`` has been deprecated,
use ``reg_slot``, ``mem_slot`` instead.
Loading

0 comments on commit f158e2b

Please sign in to comment.