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

Data processing #22

Merged
merged 56 commits into from
Apr 23, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
3d25f40
* Carved out data processor from PR #20
eggerdj Mar 26, 2021
375caa0
* Added node_output property.
eggerdj Mar 26, 2021
9994758
* Ran Black.
eggerdj Mar 26, 2021
0fc66ba
* Added a better methodology for checking the input requirements.
eggerdj Mar 26, 2021
a0cbd65
* Lint fix.
eggerdj Mar 26, 2021
839ab97
* Added population shots fix and corresponding tests.
eggerdj Mar 29, 2021
38d6675
* Unified ToReal and ToImag.
eggerdj Mar 29, 2021
0be6f1f
* Reformatted the DataProcessor to a list of nodes rather than pointers.
eggerdj Mar 31, 2021
3f95b1e
* Added history functionality to the data processor.
eggerdj Mar 31, 2021
1883a91
* Made history of data processor a property.
eggerdj Apr 1, 2021
6b6051c
* Fixed docstring.
eggerdj Apr 1, 2021
e32341e
* Added _process to the IQPart data actions.
eggerdj Apr 1, 2021
681546f
* Changed node_output to a class variable.
eggerdj Apr 1, 2021
93cdfae
* Added the option to initialize the DataProcessor with given DataAct…
eggerdj Apr 1, 2021
21374e5
* Removed Kernel and Discriminator. They will be for a future PR.
eggerdj Apr 1, 2021
5f7c082
* Added docstring from Will.
eggerdj Apr 9, 2021
8838f62
* Moved docstring.
eggerdj Apr 12, 2021
77d0bb0
Update qiskit_experiments/data_processing/base.py
eggerdj Apr 12, 2021
81ddf28
Update qiskit_experiments/data_processing/base.py
eggerdj Apr 12, 2021
6af38d9
* Aligned code to _process.
eggerdj Apr 12, 2021
bd230b5
* Made data processor callable.
eggerdj Apr 12, 2021
c9801a7
* Renamed base.py to data_action.py.
eggerdj Apr 12, 2021
ca2d365
* Made nodes callable.
eggerdj Apr 12, 2021
06095d2
* Removed history property, added call_with_history.
eggerdj Apr 12, 2021
22acc21
* Renamed Population to Probability.
eggerdj Apr 13, 2021
cefcf73
* Metadata in processed_data.
eggerdj Apr 13, 2021
a0d0903
* Refactored _process(Dict[str, Any]) -> Dict[str, Any] to _process(A…
eggerdj Apr 13, 2021
1bc7c83
* Added option to specifiy which nodes to include in the history.
eggerdj Apr 13, 2021
02c46a1
Merge branch 'main' into data_processor
eggerdj Apr 13, 2021
650d9c6
Update qiskit_experiments/data_processing/nodes.py
eggerdj Apr 16, 2021
078fd07
* Removed __init__ from DataAction.
eggerdj Apr 16, 2021
d0a85c1
Merge branch 'data_processor' of github.com:eggerdj/qiskit-experiment…
eggerdj Apr 16, 2021
4899bfa
* Added the option to turn of validation.
eggerdj Apr 16, 2021
b06e3eb
Update qiskit_experiments/data_processing/data_processor.py
eggerdj Apr 16, 2021
597d60c
* Simplified validation of IQ data.
eggerdj Apr 16, 2021
055d6cc
Update qiskit_experiments/data_processing/nodes.py
eggerdj Apr 16, 2021
a498bf0
Update qiskit_experiments/data_processing/nodes.py
eggerdj Apr 16, 2021
a35b522
* Removed unnecessary wrapping of _process.
eggerdj Apr 16, 2021
74ec8f8
* Polished docstrings and ran black.
eggerdj Apr 16, 2021
ec911bd
Update qiskit_experiments/data_processing/data_action.py
eggerdj Apr 16, 2021
83ed8ff
* Removed unnecessary code in DataProcessingError.
eggerdj Apr 16, 2021
2ffb0d3
* Rewrote doc string.
eggerdj Apr 16, 2021
657f17b
* IQ data is now of type float and not complex.
eggerdj Apr 16, 2021
58ca872
* Fixed validate issue.
eggerdj Apr 20, 2021
d749d95
* Added error message to __call__ and call_with_history.
eggerdj Apr 20, 2021
cab9339
* Improved docstring.
eggerdj Apr 20, 2021
4c9acae
* Impoved class docstring.
eggerdj Apr 20, 2021
d910933
* Changed how DataProcessor._nodes are initialized in __init__.
eggerdj Apr 20, 2021
c250ad8
* Changed behavior of empty data processor.
eggerdj Apr 20, 2021
bc00e26
* Refactored call and call_with_history to use the call_internal func…
eggerdj Apr 21, 2021
81caca7
* Fixed, lint, black, and docstrings.
eggerdj Apr 21, 2021
bcce8eb
Merge branch 'main' into data_processor
eggerdj Apr 21, 2021
2452a86
Update qiskit_experiments/data_processing/data_action.py
eggerdj Apr 22, 2021
a79d270
* Added type hint to call_with_history
eggerdj Apr 22, 2021
b19c31b
Merge branch 'data_processor' of github.com:eggerdj/qiskit-experiment…
eggerdj Apr 22, 2021
5942ede
Update qiskit_experiments/data_processing/data_processor.py
eggerdj Apr 22, 2021
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
22 changes: 22 additions & 0 deletions qiskit_experiments/data_processing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Qiskit experiments calibration data processing roots."""

from .base import DataAction
from .nodes import (
Population,
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
ToImag,
ToReal,
)

from .data_processor import DataProcessor
99 changes: 99 additions & 0 deletions qiskit_experiments/data_processing/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Defines the steps that can be used to analyse data."""

from abc import ABCMeta, abstractmethod
from typing import Any, Dict, List

from qiskit_experiments.data_processing.exceptions import DataProcessorError


class DataAction(metaclass=ABCMeta):
"""
Abstract action which is a single action done on measured data to process it.
Each subclass of DataAction must define the type of data that it accepts as input
using decorators.
"""

# Key under which the node will output the data.
__node_output__ = None

def __init__(self):
"""Create new data analysis routine."""
self._child = None
self._accepted_inputs = []
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
eggerdj marked this conversation as resolved.
Show resolved Hide resolved

@property
def node_inputs(self) -> List[str]:
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
"""Returns a list of input data that the node can process."""
return self._accepted_inputs

def add_accepted_input(self, data_key: str):
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
"""
Allows users to add an accepted input data format to this DataAction.

Args:
data_key: The key that the data action will require in the input data dict.
"""
self._accepted_inputs.append(data_key)

@abstractmethod
def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
"""
Applies the data processing step to the data.

Args:
data: the data to which the data processing step will be applied.

Returns:
processed data: The data that has been processed.
"""

def check_required(self, data: Dict[str, Any]):
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
"""Checks that the given data contains the right key.

Args:
data: The data to check for the correct keys.

Raises:
DataProcessorError: if the key is not found.
"""
for key in data.keys():
if key in self.node_inputs:
return

raise DataProcessorError(f"None of {self.node_inputs} are in the given data.")

def format_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
"""
Apply the data action of this node and call the child node's format_data method.

Args:
data: A dict containing the data. The action nodes in the data
processor will raise errors if the data does not contain the
appropriate data.

Returns:
processed data: The output data of the node contained in a dict.
"""
self.check_required(data)
processed_data = self.process(data)
processed_data["metadata"] = data.get("metadata", {})
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
return processed_data

def __repr__(self):
"""String representation of the node."""
return (
f"{self.__class__.__name__}(inputs: {self.node_inputs}, "
f"outputs: {self.__node_output__})"
)
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
105 changes: 105 additions & 0 deletions qiskit_experiments/data_processing/data_processor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Class that ties together actions on the data."""
eggerdj marked this conversation as resolved.
Show resolved Hide resolved

from typing import Any, Dict, List, Tuple, Union

from qiskit_experiments.data_processing.base import DataAction
from qiskit_experiments.data_processing.exceptions import DataProcessorError


class DataProcessor:
"""
Defines the actions done on the measured data to bring it in a form usable
by the calibration analysis classes.
"""

eggerdj marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, data_actions: List[DataAction] = None):
"""Create a chain of data processing actions.

Args:
data_actions: A list of data processing actions to construct this data processor with.
If None is given an empty DataProcessor will be created.
"""
self._nodes = []
eggerdj marked this conversation as resolved.
Show resolved Hide resolved

if data_actions:
for node in data_actions:
self.append(node)

self._history = []
eggerdj marked this conversation as resolved.
Show resolved Hide resolved

@property
def history(self) -> List[Tuple[str, Dict[str, Any]]]:
"""
Returns:
The history of the data processor. The ith tuple in the history corresponds to the
output of the ith node. Each tuple corresponds to (node name, data dict).
"""
return self._history

def append(self, node: DataAction):
"""
Append new data action node to this data processor.

Args:
node: A DataAction that will process the data.

Raises:
DataProcessorError: if the output of the last node does not match the input required
by the node to be appended.
"""
if len(self._nodes) == 0:
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
self._nodes.append(node)
else:
if self._nodes[-1].__node_output__ not in node.node_inputs:
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
raise DataProcessorError(
f"Output of node {self._nodes[-1]} is not an acceptable " f"input to {node}."
)

self._nodes.append(node)

def output_key(self) -> Union[str, None]:
"""Return the key to look for in the data output by the processor."""

if len(self._nodes) > 0:
return self._nodes[-1].__node_output__

return None

def format_data(self, data: Dict[str, Any], save_history: bool = False) -> Dict[str, Any]:
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
"""
Format the given data.

This method sequentially calls stored child data processing nodes
with its `format_data` methods. Once all child nodes have called,
input data is converted into expected data format.

Args:
data: The data, typically from an ExperimentData instance, that needs to
be processed. This dict also contains the metadata of each experiment.
save_history: If set to true the history is saved under the history property.
If set to False the history will be empty.

Returns:
processed data: The data processed by the data processor..
"""
self._history = []

for node in self._nodes:
data = node.format_data(data)

if save_history:
self._history.append((node.__class__.__name__, dict(data)))
eggerdj marked this conversation as resolved.
Show resolved Hide resolved

return data
28 changes: 28 additions & 0 deletions qiskit_experiments/data_processing/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Exceptions for data processing."""

from qiskit.exceptions import QiskitError


class DataProcessorError(QiskitError):
"""Errors raised by the calibration module."""

def __init__(self, *message):
"""Set the error message."""
super().__init__(*message)
self.message = " ".join(message)

def __str__(self):
"""Return the message."""
return repr(self.message)
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
141 changes: 141 additions & 0 deletions qiskit_experiments/data_processing/nodes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Different data analysis steps."""

from abc import abstractmethod
from typing import Any, Dict, Optional, Tuple
import numpy as np

from qiskit_experiments.data_processing.base import DataAction


class IQPart(DataAction):
"""Abstract class for IQ data post-processing."""

def __init__(self, scale: Optional[float] = 1.0, average: bool = False):
"""
Args:
scale: scale by which to multiply the real part of the data.
average: if True the single-shots are averaged.
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
"""
self.scale = scale
self.average = average
super().__init__()
self._accepted_inputs = ["memory"]

@abstractmethod
def _process(self, point: Tuple[float, float]) -> float:
"""Defines how the IQ point will be processed.

Args:
point: An IQ point as a tuple of two float, i.e. (real, imaginary).

Returns:
Processed IQ point.
"""

def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""
Modifies the data inplace by taking the real part of the memory and
scaling it by the given factor.

Args:
data: The data dict. IQ data is stored under memory.

Returns:
processed data: A dict with the data.
"""

# Single shot data
if isinstance(data["memory"][0][0], list):
new_mem = []
for shot in data["memory"]:
new_mem.append([self.scale * self._process(iq_point) for iq_point in shot])

if self.average:
new_mem = list(np.mean(np.array(new_mem), axis=0))
eggerdj marked this conversation as resolved.
Show resolved Hide resolved

# Averaged data
else:
new_mem = [self.scale * self._process(iq_point) for iq_point in data["memory"]]

return {self.__node_output__: new_mem}
eggerdj marked this conversation as resolved.
Show resolved Hide resolved


class ToReal(IQPart):
"""IQ data post-processing. Isolate the real part of the IQ data."""

__node_output__ = "memory_real"

def _process(self, point: Tuple[float, float]) -> float:
"""Defines how the IQ point will be processed.

Args:
point: An IQ point as a tuple of two float, i.e. (real, imaginary).

Returns:
The real part of the IQ point.
"""
return point[0]


class ToImag(IQPart):
"""IQ data post-processing. Isolate the imaginary part of the IQ data."""

__node_output__ = "memory_imag"

def _process(self, point: Tuple[float, float]) -> float:
"""Defines how the IQ point will be processed.

Args:
point: An IQ point as a tuple of two float, i.e. (real, imaginary).

Returns:
The imaginary part of the IQ point.
"""
return point[1]

eggerdj marked this conversation as resolved.
Show resolved Hide resolved

class Population(DataAction):
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
"""Count data post processing. This returns population."""

__node_output__ = "populations"

def __init__(self):
"""Initialize a counts to population data conversion."""
super().__init__()
self._accepted_inputs = ["counts"]

def process(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""
Args:
data: The data dictionary. This will modify the dict in place,
taking the data under counts and adding the corresponding
populations.

Returns:
processed data: A dict with the populations.
"""

counts = data.get("counts")

populations = np.zeros(len(list(counts.keys())[0]))

shots = 0
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
for bit_str, count in counts.items():
shots += count
for ind, bit in enumerate(bit_str):
if bit == "1":
populations[ind] += count

eggerdj marked this conversation as resolved.
Show resolved Hide resolved
return {self.__node_output__: populations / shots}
Empty file.
Loading