Skip to content

Commit

Permalink
get_memory for pulse measurement level 0 and 1 (Qiskit#2106)
Browse files Browse the repository at this point in the history
* Add meas_level to result schema.

* Update changelog.

* Added meas_level to marshmallow schema.

* Remove required from result model.

* Added scaffolding to support memory formatting for 0,1,2. Still need to add formatting methods.

* Update result handling to work with both circuits and schedules.

* Adding result processing for pulse.

* Added measurement level 0 and 1 result handling.

* Removed header from measurement level 0 and 1. Linting

* Remove dummy schedule.
  • Loading branch information
taalexander authored and kdk committed Apr 16, 2019
1 parent 58afe7f commit 8f90432
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 56 deletions.
1 change: 1 addition & 0 deletions qiskit/pulse/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# the LICENSE.txt file in the root directory of this source tree.

"""Module for Pulses."""
from qiskit.pulse.schedule import Schedule
from qiskit.pulse.channels import DeviceSpecification
from qiskit.pulse.commands import (Acquire, FrameChange, PersistentValue,
SamplePulse, Snapshot,
Expand Down
116 changes: 100 additions & 16 deletions qiskit/result/postprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import numpy as np

from qiskit.exceptions import QiskitError


def _hex_to_bin(hexstring):
"""Convert hexadecimal readouts (memory) to binary readouts."""
Expand All @@ -31,7 +33,7 @@ def _separate_bitstring(bitstring, creg_sizes):
return ' '.join(substrings)


def format_memory(memory, header):
def format_counts_memory(shot_memory, header=None):
"""
Format a single bitstring (memory) from a single shot experiment.
Expand All @@ -40,25 +42,107 @@ def format_memory(memory, header):
- Spaces are inserted at register divisions.
Args:
memory (str): result of a single experiment.
shot_memory (str): result of a single experiment.
header (dict): the experiment header dictionary containing
useful information for postprocessing.
useful information for postprocessing. creg_sizes
are a nested list where the inner element is a list
of creg name, creg size pairs. memory_slots is an integers
specifying the number of total memory_slots in the experiment.
Returns:
dict: a formatted memory
"""
creg_sizes = header.get('creg_sizes')
memory_slots = header.get('memory_slots')
if memory.startswith('0x'):
memory = _hex_to_bin(memory)
if memory_slots:
memory = _pad_zeros(memory, memory_slots)
if creg_sizes:
memory = _separate_bitstring(memory, creg_sizes)
return memory


def format_counts(counts, header):
if shot_memory.startswith('0x'):
shot_memory = _hex_to_bin(shot_memory)
if header:
creg_sizes = header.get('creg_sizes', None)
memory_slots = header.get('memory_slots', None)
if memory_slots:
shot_memory = _pad_zeros(shot_memory, memory_slots)
if creg_sizes and memory_slots:
shot_memory = _separate_bitstring(shot_memory, creg_sizes)
return shot_memory


def _list_to_complex_array(complex_list):
"""Convert nested list of shape (..., 2) to complex numpy array with shape (...)
Args:
complex_list (list): List to convert.
Returns:
np.ndarray: Complex numpy aray
Raises:
QiskitError: If inner most array of input nested list is not of length 2.
"""
arr = np.asarray(complex_list, dtype=np.complex_)
if not arr.shape[-1] == 2:
raise QiskitError('Inner most nested list is not of length 2.')

return arr[..., 0] + 1j*arr[..., 1]


def format_level_0_memory(memory):
""" Format an experiment result memory object for measurement level 0.
Args:
memory (list): Memory from experiment with `meas_level==1`. `avg` or
`single` will be inferred from shape of result memory.
Returns:
np.ndarray: Measurement level 0 complex numpy array
Raises:
QiskitError: If the returned numpy array does not have 2 (avg) or 3 (single)
indicies.
"""
formatted_memory = _list_to_complex_array(memory)
# infer meas_return from shape of returned data.
if not 2 <= len(formatted_memory.shape) <= 3:
raise QiskitError('Level zero memory is not of correct shape.')
return formatted_memory


def format_level_1_memory(memory):
""" Format an experiment result memory object for measurement level 1.
Args:
memory (list): Memory from experiment with `meas_level==1`. `avg` or
`single` will be inferred from shape of result memory.
Returns:
np.ndarray: Measurement level 1 complex numpy array
Raises:
QiskitError: If the returned numpy array does not have 1 (avg) or 2 (single)
indicies.
"""
formatted_memory = _list_to_complex_array(memory)
# infer meas_return from shape of returned data.
if not 1 <= len(formatted_memory.shape) <= 2:
raise QiskitError('Level one memory is not of correct shape.')
return formatted_memory


def format_level_2_memory(memory, header=None):
""" Format an experiment result memory object for measurement level 2.
Args:
memory (list): Memory from experiment with `meas_level==2` and `memory==True`.
header (dict): the experiment header dictionary containing
useful information for postprocessing.
Returns:
list[str]: List of bitstrings
"""
memory_list = []
for shot_memory in memory:
memory_list.append(format_counts_memory(shot_memory, header))
return memory_list


def format_counts(counts, header=None):
"""Format a single experiment result coming from backend to present
to the Qiskit user.
Expand All @@ -72,7 +156,7 @@ def format_counts(counts, header):
"""
counts_dict = {}
for key, val in counts.items():
key = format_memory(key, header)
key = format_counts_memory(key, header)
counts_dict[key] = val
return counts_dict

Expand Down
111 changes: 71 additions & 40 deletions qiskit/result/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
"""Model for schema-conformant Results."""

from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.pulse.schedule import Schedule
from qiskit.exceptions import QiskitError

from qiskit.validation.base import BaseModel, bind_schema
from .postprocess import (format_counts, format_statevector,
format_unitary, format_memory)
from qiskit.result import postprocess
from .models import ResultSchema


Expand Down Expand Up @@ -45,24 +45,26 @@ def __init__(self, backend_name, backend_version, qobj_id, job_id, success,

super().__init__(**kwargs)

def data(self, circuit=None):
def data(self, experiment=None):
"""Get the raw data for an experiment.
Note this data will be a single classical and quantum register and in a
format required by the results schema. We recomened that most users use
the get_xxx method, and the data will be post-processed for the data type.
Args:
circuit (str or QuantumCircuit or int or None): the index of the
experiment (str or QuantumCircuit or Schedule or int or None): the index of the
experiment. Several types are accepted for convenience::
* str: the name of the experiment.
* QuantumCircuit: the name of the instance will be used.
* QuantumCircuit: the name of the circuit instance will be used.
* Schedule: the name of the schedule instance will be used.
* int: the position of the experiment.
* None: if there is only one experiment, returns it.
Returns:
dict: A dictionary of results data for an experiment. The data
depends on the backend it ran on.
depends on the backend it ran on and the settings of `meas_level`,
`meas_return` and `memory`.
QASM backends return a dictionary of dictionary with the key
'counts' and with the counts, with the second dictionary keys
Expand Down Expand Up @@ -92,40 +94,63 @@ def data(self, circuit=None):
QiskitError: if data for the experiment could not be retrieved.
"""
try:
return self._get_experiment(circuit).data.to_dict()
return self._get_experiment(experiment).data.to_dict()
except (KeyError, TypeError):
raise QiskitError('No data for circuit "{0}"'.format(circuit))
raise QiskitError('No data for experiment "{0}"'.format(experiment))

def get_memory(self, circuit=None):
def get_memory(self, experiment=None):
"""Get the sequence of memory states (readouts) for each shot
The data from the experiment is a list of format
['00000', '01000', '10100', '10100', '11101', '11100', '00101', ..., '01010']
Args:
circuit (str or QuantumCircuit or int or None): the index of the
experiment (str or QuantumCircuit or Schedule or int or None): the index of the
experiment, as specified by ``data()``.
Returns:
List[str]: the list of each outcome, formatted according to
registers in circuit.
List[str] or np.ndarray: Either the list of each outcome, formatted according to
registers in circuit or a complex numpy np.darray with shape:
| `meas_level` | `meas_return` | shape |
|--------------|---------------|---------------------------------------------------|
| 0 | `single` | np.ndarray[shots, memory_slots, memory_slot_size] |
| 0 | `avg` | np.ndarray[memory_slots, memory_slot_size] |
| 1 | `single` | np.ndarray[shots, memory_slots] |
| 1 | `avg` | np.ndarray[memory_slots] |
| 2 | `memory=True` | list |
Raises:
QiskitError: if there is no memory data for the circuit.
"""
try:
header = self._get_experiment(circuit).header.to_dict()
memory_list = []
for memory in self.data(circuit)['memory']:
memory_list.append(format_memory(memory, header))
return memory_list
exp_result = self._get_experiment(experiment)

try: # header is not available
header = exp_result.header.to_dict()
except (AttributeError, QiskitError):
header = None

meas_level = exp_result.meas_level

memory = self.data(experiment)['memory']

if meas_level == 2:
return postprocess.format_level_2_memory(memory, header)
elif meas_level == 1:
return postprocess.format_level_1_memory(memory)
elif meas_level == 0:
return postprocess.format_level_0_memory(memory)
else:
raise QiskitError('Measurement level {0} is not supported'.format(meas_level))

except KeyError:
raise QiskitError('No memory for circuit "{0}".'.format(circuit))
raise QiskitError('No memory for experiment "{0}".'.format(experiment))

def get_counts(self, circuit=None):
def get_counts(self, experiment=None):
"""Get the histogram data of an experiment.
Args:
circuit (str or QuantumCircuit or int or None): the index of the
experiment (str or QuantumCircuit or Schedule or int or None): the index of the
experiment, as specified by ``get_data()``.
Returns:
Expand All @@ -138,16 +163,22 @@ def get_counts(self, circuit=None):
QiskitError: if there are no counts for the experiment.
"""
try:
return format_counts(self.data(circuit)['counts'],
self._get_experiment(circuit).header.to_dict())
exp = self._get_experiment(experiment)
try:
header = exp.header.to_dict()
except (AttributeError, QiskitError): # header is not available
header = None

return postprocess.format_counts(self.data(experiment)['counts'],
header)
except KeyError:
raise QiskitError('No counts for circuit "{0}"'.format(circuit))
raise QiskitError('No counts for experiment "{0}"'.format(experiment))

def get_statevector(self, circuit=None, decimals=None):
def get_statevector(self, experiment=None, decimals=None):
"""Get the final statevector of an experiment.
Args:
circuit (str or QuantumCircuit or int or None): the index of the
experiment (str or QuantumCircuit or Schedule or int or None): the index of the
experiment, as specified by ``data()``.
decimals (int): the number of decimals in the statevector.
If None, does not round.
Expand All @@ -159,16 +190,16 @@ def get_statevector(self, circuit=None, decimals=None):
QiskitError: if there is no statevector for the experiment.
"""
try:
return format_statevector(self.data(circuit)['statevector'],
decimals=decimals)
return postprocess.format_statevector(self.data(experiment)['statevector'],
decimals=decimals)
except KeyError:
raise QiskitError('No statevector for circuit "{0}"'.format(circuit))
raise QiskitError('No statevector for experiment "{0}"'.format(experiment))

def get_unitary(self, circuit=None, decimals=None):
def get_unitary(self, experiment=None, decimals=None):
"""Get the final unitary of an experiment.
Args:
circuit (str or QuantumCircuit or int or None): the index of the
experiment (str or QuantumCircuit or Schedule or int or None): the index of the
experiment, as specified by ``data()``.
decimals (int): the number of decimals in the unitary.
If None, does not round.
Expand All @@ -181,23 +212,23 @@ def get_unitary(self, circuit=None, decimals=None):
QiskitError: if there is no unitary for the experiment.
"""
try:
return format_unitary(self.data(circuit)['unitary'],
decimals=decimals)
return postprocess.format_unitary(self.data(experiment)['unitary'],
decimals=decimals)
except KeyError:
raise QiskitError('No unitary for circuit "{0}"'.format(circuit))
raise QiskitError('No unitary for experiment "{0}"'.format(experiment))

def _get_experiment(self, key=None):
"""Return a single experiment result from a given key.
Args:
key (str or QuantumCircuit or int or None): the index of the
key (str or QuantumCircuit or Schedule or int or None): the index of the
experiment, as specified by ``get_data()``.
Returns:
ExperimentResult: the results for an experiment.
Raises:
QiskitError: if there is no data for the circuit, or an unhandled
QiskitError: if there is no data for the experiment, or an unhandled
error occurred while fetching the data.
"""
if not self.success:
Expand All @@ -208,17 +239,17 @@ def _get_experiment(self, key=None):
if key is None:
if len(self.results) != 1:
raise QiskitError(
'You have to select a circuit when there is more than '
'You have to select a circuit or schedule when there is more than '
'one available')
else:
key = 0

key = 0

# Key is an integer: return result by index.
if isinstance(key, int):
return self.results[key]

# Key is a QuantumCircuit or str: retrieve result by name.
if isinstance(key, QuantumCircuit):
# Key is a QuantumCircuit/Schedule or str: retrieve result by name.
if isinstance(key, (QuantumCircuit, Schedule)):
key = key.name

try:
Expand Down
8 changes: 8 additions & 0 deletions test/python/result/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-

# Copyright 2019, IBM.
#
# This source code is licensed under the Apache License, Version 2.0 found in
# the LICENSE.txt file in the root directory of this source tree.

"""Qiskit result integration tests."""
Loading

0 comments on commit 8f90432

Please sign in to comment.