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

Remove marshmallow from result/ and requirements #4030

Merged
merged 24 commits into from
May 7, 2020
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c542e11
Remove marshmallow from result/ and requirements
mtreinish Mar 26, 2020
f4add26
Merge branch 'master' into remove-marshmallow-forever
mtreinish Apr 6, 2020
43648f5
Merge branch 'master' into remove-marshmallow-forever
mtreinish Apr 10, 2020
b808d20
Fixes from testing
mtreinish Apr 10, 2020
5244186
Merge branch 'master' into remove-marshmallow-forever
mtreinish Apr 10, 2020
59b9c2a
Merge branch 'master' into remove-marshmallow-forever
mtreinish Apr 15, 2020
f8c7b19
Merge branch 'master' into remove-marshmallow-forever
mtreinish Apr 16, 2020
1f5dead
Abandon SimpleNamespace for Result and ExperimentResult
mtreinish Apr 16, 2020
d5d0843
Use correct header type in tests
mtreinish Apr 16, 2020
8ef7884
Fix lint
mtreinish Apr 16, 2020
ce78dd7
Merge branch 'master' into remove-marshmallow-forever
mtreinish Apr 20, 2020
03f5a34
Merge branch 'master' into remove-marshmallow-forever
mtreinish Apr 21, 2020
2b2d5a0
Merge branch 'master' into remove-marshmallow-forever
mtreinish Apr 21, 2020
c8381dd
Merge branch 'master' into remove-marshmallow-forever
mtreinish Apr 28, 2020
46ea8d2
Merge branch 'master' into remove-marshmallow-forever
mtreinish May 4, 2020
edabd87
Fix docstring
mtreinish May 4, 2020
33be3e2
Add release notes
mtreinish May 4, 2020
ef433b6
Merge branch 'master' into remove-marshmallow-forever
mtreinish May 5, 2020
428b312
Merge branch 'master' into remove-marshmallow-forever
mtreinish May 5, 2020
514d58f
Fix docs issues from review comments
mtreinish May 5, 2020
808b7fb
Merge branch 'master' into remove-marshmallow-forever
mtreinish May 6, 2020
163734c
Merge branch 'master' into remove-marshmallow-forever
mtreinish May 6, 2020
11fc0c4
Merge branch 'master' into remove-marshmallow-forever
mergify[bot] May 7, 2020
4a8c26b
Merge branch 'master' into remove-marshmallow-forever
mtreinish May 7, 2020
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
241 changes: 158 additions & 83 deletions qiskit/result/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,88 +14,77 @@

"""Schema and helper models for schema-conformant Results."""

from marshmallow.validate import Length, OneOf, Regexp, Range
import copy

from qiskit.validation.base import BaseModel, BaseSchema, ObjSchema, bind_schema
from qiskit.validation.fields import Complex, ByType
from qiskit.validation.fields import Boolean, DateTime, Integer, List, Nested
from qiskit.validation.fields import Raw, String, NumpyArray
from qiskit.validation.validate import PatternProperties
from qiskit.qobj.utils import MeasReturnType, MeasLevel


class ExperimentResultDataSchema(BaseSchema):
"""Schema for ExperimentResultData."""

counts = Nested(ObjSchema,
validate=PatternProperties(
{Regexp('^0x([0-9A-Fa-f])+$'): Integer()}))
snapshots = Nested(ObjSchema)
memory = List(Raw(),
validate=Length(min=1))
statevector = NumpyArray(Complex(),
validate=Length(min=1))
unitary = NumpyArray(NumpyArray(Complex(),
validate=Length(min=1)),
validate=Length(min=1))


class ExperimentResultSchema(BaseSchema):
"""Schema for ExperimentResult."""

# Required fields.
shots = ByType([Integer(), List(Integer(validate=Range(min=1)),
validate=Length(equal=2))],
required=True)
success = Boolean(required=True)
data = Nested(ExperimentResultDataSchema, required=True)

# Optional fields.
status = String()
seed = Integer()
meas_level = Integer(validate=OneOf(choices=(MeasLevel.RAW,
MeasLevel.KERNELED,
MeasLevel.CLASSIFIED)))
meas_return = String(validate=OneOf(choices=(MeasReturnType.AVERAGE,
MeasReturnType.SINGLE)))
header = Nested(ObjSchema)


class ResultSchema(BaseSchema):
"""Schema for Result."""

# Required fields.
backend_name = String(required=True)
backend_version = String(required=True,
validate=Regexp('[0-9]+.[0-9]+.[0-9]+$'))
qobj_id = String(required=True)
job_id = String(required=True)
success = Boolean(required=True)
results = Nested(ExperimentResultSchema, required=True, many=True)

# Optional fields.
date = DateTime()
status = String()
header = Nested(ObjSchema)


@bind_schema(ExperimentResultDataSchema)
class ExperimentResultData(BaseModel):
"""Model for ExperimentResultData.

Please note that this class only describes the required fields. For the
full description of the model, please check
``ExperimentResultDataSchema``.
"""
pass


@bind_schema(ExperimentResultSchema)
class ExperimentResult(BaseModel):
"""Model for ExperimentResult.

Please note that this class only describes the required fields. For the
full description of the model, please check ``ExperimentResultSchema``.
from qiskit.qobj import QobjExperimentHeader
from qiskit.exceptions import QiskitError


class ExperimentResultData:
"""Class representing experiment result data"""

def __init__(self, counts=None, snapshots=None, memory=None,
statevector=None, unitary=None):
"""Initialize an ExperimentalResult Data class

Args:
counts (dict): A dictionary where the keys are the result in
hexadecimal as string of the format "0xff" and the value
is the number of counts for that result
snapshots (dict): A dictionary where the key is the snapshot
slot and the value is a dictionary of the snapshots for
that slot.
memory (list): A list of results per shot if the run had
memory enabled
statevector (list or numpy.array): A list or numpy array of the
statevector result
unitary (list or numpy.array): A list or numpy arrray of the
unitary result
"""

if counts is not None:
self.counts = counts
if snapshots is not None:
self.snapshots = snapshots
if memory is not None:
self.memory = memory
if statevector is not None:
self.statevector = statevector
if unitary is not None:
self.unitary = unitary

def to_dict(self):
"""Return a dictionary format representation of the ExperimentResultData

Returns:
dict: The dictionary form of the ExperimentResultData
"""
out_dict = {}
for field in ['counts', 'snapshots', 'memory', 'statevector',
'unitary']:
if hasattr(self, field):
out_dict[field] = getattr(self, field)
return out_dict

@classmethod
def from_dict(cls, data):
"""Create a new ExperimentResultData object from a dictionary.

Args:
data (dict): A dictionary representing the ExperimentResultData to
create. It will be in the same format as output by
:meth:`to_dict`
Returns:
ExperimentResultData: The ``ExperimentResultData`` object from the
input dictionary.
"""
in_data = copy.copy(data)
return cls(**in_data)


class ExperimentResult:
"""Class representing an Experiment Result.

Attributes:
shots (int or tuple): the starting and ending shot for this data.
Expand All @@ -104,10 +93,96 @@ class ExperimentResult(BaseModel):
meas_level (int): Measurement result level.
"""

def __init__(self, shots, success, data, meas_level=MeasLevel.CLASSIFIED, **kwargs):
_metadata = {}

def __init__(self, shots, success, data, meas_level=MeasLevel.CLASSIFIED,
status=None, seed=None, meas_return=None, header=None,
**kwargs):
"""Initialize an ExperimentResult object.

Args:
shots(int or tuple): if an integer the number of shots or if a
tuple the starting and ending shot for this data
success (bool): True if the experiment was successful
data (ExperimentResultData): The data for the experiment's
result
meas_level (int): Measurement result level
status (str): The status of the experiment
seed (int): The seed used for simulation (if run on a simulator)
meas_return (str): The type of measurement returned
header (qiskit.qobj.QobjExperimentHeader): A free form dictionary
header for the experiment
kwargs: Arbitrary extra fields

Raises:
QiskitError: If meas_return or meas_level are not valid values
"""
self._metadata = {}
self.shots = shots
self.success = success
self.data = data
self.meas_level = meas_level

super().__init__(**kwargs)
if header is not None:
self.header = header
if status is not None:
self.status = status
if seed is not None:
self.seed = seed
if meas_return is not None:
if meas_return not in list(MeasReturnType):
raise QiskitError('%s not a valid meas_return value')
self.meas_return = meas_return
self._metadata.update(kwargs)

def __getattr__(self, name):
try:
return self._metadata[name]
except KeyError:
raise AttributeError('Attribute %s is not defined' % name)

def to_dict(self):
"""Return a dictionary format representation of the ExperimentResult

Returns:
dict: The dictionary form of the ExperimentResult
"""
out_dict = {
'shots': self.shots,
'success': self.success,
'data': self.data.to_dict(),
'meas_level': self.meas_level,
}
if hasattr(self, 'header'):
out_dict['header'] = self.header.to_dict()
if hasattr(self, 'status'):
out_dict['status'] = self.status
if hasattr(self, 'seed'):
out_dict['seed'] = self.seed
if hasattr(self, 'meas_return'):
out_dict['meas_return'] = self.meas_return
out_dict.update(self._metadata)
return out_dict

@classmethod
def from_dict(cls, data):
"""Create a new ExperimentResult object from a dictionary.

Args:
data (dict): A dictionary representing the ExperimentResult to
create. It will be in the same format as output by
:meth:`to_dict`

Returns:
ExperimentResult: The ``ExperimentResult`` object from the input
dictionary.
"""

in_data = copy.copy(data)
data_obj = ExperimentResultData.from_dict(in_data.pop('data'))
if 'header' in in_data:
in_data['header'] = QobjExperimentHeader.from_dict(
in_data.pop('header'))
shots = in_data.pop('shots')
success = in_data.pop('success')

return cls(shots, success, data_obj, **in_data)
4 changes: 4 additions & 0 deletions qiskit/result/postprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ def format_statevector(vec, decimals=None):
Returns:
list[complex]: a list of python complex numbers.
"""
if isinstance(vec, np.ndarray):
return vec
num_basis = len(vec)
vec_complex = np.zeros(num_basis, dtype=complex)
for i in range(num_basis):
Expand All @@ -204,6 +206,8 @@ def format_unitary(mat, decimals=None):
Returns:
list[list[complex]]: a matrix of complex numbers
"""
if isinstance(mat, np.ndarray):
return mat
num_basis = len(mat)
mat_complex = np.zeros((num_basis, num_basis), dtype=complex)
for i, vec in enumerate(mat):
Expand Down
74 changes: 64 additions & 10 deletions qiskit/result/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,21 @@

"""Model for schema-conformant Results."""

import copy

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

from qiskit.validation.base import BaseModel, bind_schema
from qiskit.result.models import ExperimentResult
from qiskit.result import postprocess
from qiskit.qobj.utils import MeasLevel
from .models import ResultSchema
from qiskit.qobj import QobjHeader


@bind_schema(ResultSchema)
class Result(BaseModel):
class Result:
"""Model for Results.

Please note that this class only describes the required fields. For the
full description of the model, please check ``ResultSchema``.

Attributes:
backend_name (str): backend name.
backend_version (str): backend version, in the form X.Y.Z.
Expand All @@ -43,16 +40,73 @@ class Result(BaseModel):
experiments of the input qobj
"""

_metadata = {}

def __init__(self, backend_name, backend_version, qobj_id, job_id, success,
results, **kwargs):
results, date=None, status=None, header=None, **kwargs):
self._metadata = {}
self.backend_name = backend_name
self.backend_version = backend_version
self.qobj_id = qobj_id
self.job_id = job_id
self.success = success
self.results = results
if date is not None:
self.date = date
if status is not None:
self.status = status
if header is not None:
self.header = header
self._metadata.update(kwargs)

def to_dict(self):
"""Return a dictionary format representation of the Result

Returns:
dict: The dictionary form of the Result
"""
out_dict = {
'backend_name': self.backend_name,
'backend_version': self.backend_version,
'qobj_id': self.qobj_id,
'job_id': self.job_id,
'success': self.success,
'results': [x.to_dict() for x in self.results]
}
if hasattr(self, 'date'):
out_dict['date'] = self.date
if hasattr(self, 'status'):
out_dict['status'] = self.status
if hasattr(self, 'header'):
out_dict['header'] = self.header.to_dict()
out_dict.update(self._metadata)
return out_dict

def __getattr__(self, name):
try:
return self._metadata[name]
except KeyError:
raise AttributeError('Attribute %s is not defined' % name)

@classmethod
def from_dict(cls, data):
"""Create a new ExperimentResultData object from a dictionary.

Args:
data (dict): A dictionary representing the Result to create. It
will be in the same format as output by
:meth:`to_dict`.
Returns:
Result: The ``Result`` object from the input dictionary.

"""

super().__init__(**kwargs)
in_data = copy.copy(data)
in_data['results'] = [
ExperimentResult.from_dict(x) for x in in_data.pop('results')]
if 'header' in in_data:
in_data['header'] = QobjHeader.from_dict(in_data.pop('header'))
return cls(**in_data)

def data(self, experiment=None):
"""Get the raw data for an experiment.
Expand Down
4 changes: 2 additions & 2 deletions qiskit/result/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from functools import reduce
from re import match
from qiskit.validation.base import Obj

from qiskit.exceptions import QiskitError


Expand Down Expand Up @@ -46,7 +46,7 @@ def marginal_counts(result, indices=None):
new_counts_hex = {}
for k, v in new_counts.items():
new_counts_hex[_bin_to_hex(k)] = v
experiment_result.data.counts = Obj(**new_counts_hex)
experiment_result.data.counts = new_counts_hex
experiment_result.header.memory_slots = len(indices)
else:
counts = result
Expand Down
Loading