Skip to content

Commit

Permalink
Merge pull request #302 from victor-onofre/dev
Browse files Browse the repository at this point in the history
- Integrate Mitiq into OpenQAOA to allow error mitigation techniques to take place during the execution of the QAOA. We have created a wrapper focusing on the ZNE technique following a similar structure to the SPAM Twirling wrapper.
- This is a project born from the Quantum Open Source Foundation Mentorship program
- New dependency to add: Mitiq
- Fixes Integrate ZNE from Mitiq into OpenQAOA #207
  • Loading branch information
KilianPoirier authored May 28, 2024
2 parents d79eb41 + 0f8f624 commit 7f84b11
Show file tree
Hide file tree
Showing 13 changed files with 995 additions and 30 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
# OpenQAOA

A multi-backend python library for quantum optimization using QAOA on Quantum computers and Quantum computer simulators. Check out the OpenQAOA website at [https://openqaoa.entropicalabs.com/](https://openqaoa.entropicalabs.com/)


**OpenQAOA is currently in OpenBeta.**

Expand Down
2 changes: 1 addition & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ Contents
notebooks/13_optimizers.ipynb
notebooks/14_qaoa_benchmark.ipynb
notebooks/X_dumping_data.ipynb

notebooks/15_Zero_Noise_Extrapolation.ipynb

Indices and tables
==================
Expand Down
545 changes: 545 additions & 0 deletions examples/15_Zero_Noise_Extrapolation.ipynb

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def get_counts(self, params: QAOAVariationalBaseParams, n_shots=None) -> dict:
A dictionary with the bitstring as the key and the number of counts
as its value.
"""

n_shots = self.n_shots if n_shots == None else n_shots

circuit = self.qaoa_circuit(params)
Expand Down
51 changes: 41 additions & 10 deletions src/openqaoa-core/openqaoa/algorithms/baseworkflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
from .workflow_properties import (
BackendProperties,
ErrorMitigationProperties,
MitiqZNEProperties,
SpamProperties,
ClassicalOptimizer,
)
from ..backends.devices_core import DeviceBase, DeviceLocal
Expand Down Expand Up @@ -98,7 +100,7 @@ def __init__(self, device=DeviceLocal("vectorized")):
self.classical_optimizer = ClassicalOptimizer()
self.local_simulators = list(DEVICE_NAME_TO_OBJECT_MAPPER.keys())
self.cloud_provider = list(DEVICE_ACCESS_OBJECT_MAPPER.keys())
self.available_error_mitigation_techniques = ["spam_twirling"]
self.available_error_mitigation_techniques = ["spam_twirling","mitiq_zne"]
self.compiled = False

# Initialize the identifier stamps, we initialize all the stamps needed to None
Expand Down Expand Up @@ -271,25 +273,54 @@ def set_error_mitigation_properties(self, **kwargs):
Parameters
----------
error_mitigation_technique: str
The specific technique used to mitigate the errors. Only a simple state preparation and measurement twirling with bitflip averages, under the name "spam_twirling" is currently supported.
The specific technique used to mitigate the errors. Currently, the availables techniques are:
* A simple state preparation and measurement twirling with bitflip averages, under the name "spam_twirling".
* Zero Noise Extrapolation (ZNE), integrated from Mitiq framework, under the name "mitiq_zne".
n_batches: int
The number of batches specifies the different negating schedules at random. Total number of shots is distributed accordingly.
Used in "spam_twirling". The number of batches specifies the different negating schedules at random. Total number of shots is distributed accordingly.
calibration_data_location: str
The location of the json file containing calibration data. For spam twirling this is the measurement outcomes of an empty circuit under the bit-flip averaging.
Used in "spam_twirling". The location of the json file containing calibration data. This is the measurement outcomes of an empty circuit under the bit-flip averaging.
factory: str
Used in "mitiq_zne". The name of the zero-noise extrapolation method. Supported values: "Richardson", "Linear", "Poly", "Exp", "PolyExp", "AdaExp", "FakeNodes".
scaling: str
Used in "mitiq_zne". The name of the function for scaling the noise of a quantum circuit. Supported values: "fold_gates_at_random", "fold_gates_from_right", "fold_gates_from_left".
scale_factors: List[int]
Used in "mitiq_zne". Sequence of noise scale factors at which expectation values should be measured.
For factory = "AdaExp", just the first element of the list will be considered.
order: int
Used in "mitiq_zne". Extrapolation order (degree of the polynomial to fit). It cannot exceed len(scale_factors) - 1, and it must be greater than or equal to 1.
Only used for factory = "Poly" or "PolyExp".
steps: int
Used in "mitiq_zne". The number of optimization steps. At least 3 are necessary.
Only used for factory = "AdaExp".
"""

# validate a supported error mitigation technique
if kwargs["error_mitigation_technique"].lower() in self.available_error_mitigation_techniques:
pass
else:
raise ValueError(
f"Specified error mitigation technique is not supported"
)

# get the ErrorMitigationProperty structure to validate
error_mitigation_technique = kwargs["error_mitigation_technique"].lower()
if error_mitigation_technique == 'mitiq_zne':
error_mitigation_properties = MitiqZNEProperties
elif error_mitigation_technique == 'spam_twirling':
error_mitigation_properties = SpamProperties
self.error_mitigation_properties = error_mitigation_properties()
# validate ErrorMitigationProperty structure
for key, value in kwargs.items():
if hasattr(self.error_mitigation_properties, key) and (
kwargs["error_mitigation_technique"].lower()
in self.available_error_mitigation_techniques
):
if hasattr(self.error_mitigation_properties, key):
pass # setattr(self.error_mitigation, key, value)
else:
raise ValueError(
f"Specified argument `{value}` for `{key}` in set_error_mitigation_properties is not supported"
)

self.error_mitigation_properties = error_mitigation_properties(**kwargs)

self.error_mitigation_properties = ErrorMitigationProperties(**kwargs)
return None

@check_compiled
Expand Down
2 changes: 1 addition & 1 deletion src/openqaoa-core/openqaoa/algorithms/qaoa/qaoa_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ def get_counts(measurement_outcomes):
The count dictionary obtained either throught the statevector or
the actual measurement counts.
"""

if isinstance(measurement_outcomes, type(np.array([]))):
measurement_outcomes = qaoa_probabilities(measurement_outcomes)

Expand Down
19 changes: 15 additions & 4 deletions src/openqaoa-core/openqaoa/algorithms/qaoa/qaoa_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
ground_state_hamiltonian,
)
from ...optimizers.qaoa_optimizer import get_optimizer
from ...backends.wrapper import SPAMTwirlingWrapper
from ...backends.wrapper import SPAMTwirlingWrapper,ZNEWrapper


class QAOA(Workflow):
Expand Down Expand Up @@ -261,7 +261,7 @@ def compile(
**backend_dict,
)

# Implementing SPAM Twirling error mitigation requires wrapping the backend.
# Implementing SPAM Twirling and MITIQs error mitigation requires wrapping the backend.
# However, the BaseWrapper can have many more use cases.
if (
self.error_mitigation_properties.error_mitigation_technique
Expand All @@ -272,7 +272,19 @@ def compile(
n_batches=self.error_mitigation_properties.n_batches,
calibration_data_location=self.error_mitigation_properties.calibration_data_location,
)

elif(
self.error_mitigation_properties.error_mitigation_technique
== "mitiq_zne"
):
self.backend = ZNEWrapper(
backend=self.backend,
factory=self.error_mitigation_properties.factory,
scaling=self.error_mitigation_properties.scaling,
scale_factors=self.error_mitigation_properties.scale_factors,
order=self.error_mitigation_properties.order,
steps=self.error_mitigation_properties.steps
)

self.optimizer = get_optimizer(
vqa_object=self.backend,
variational_params=self.variate_params,
Expand Down Expand Up @@ -402,7 +414,6 @@ def evaluate_circuit(
evaluated on a state simulator) or the counts of the QAOA circuit output
(if the QAOA circuit is evaluated on a QPU or shot-based simulator)
"""

# before evaluating the circuit we check that the QAOA object has been compiled
if self.compiled is False:
raise ValueError("Please compile the QAOA before optimizing it!")
Expand Down
73 changes: 63 additions & 10 deletions src/openqaoa-core/openqaoa/algorithms/workflow_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,15 +264,34 @@ def __init__(
# f"cvar_alpha must be between 0 and 1. Received {value}.")
# self._cvar_alpha = value


class ErrorMitigationProperties(WorkflowProperties):
"""
Optional, choose an error mitigation technique for the QAOA circuit. Currently supports only SPAM Twirling.
Optional, choose an error mitigation technique for the QAOA circuit.
Parameters
----------
error_mitigation_technique: str
The name of the error mitigation technique. Currently supported values: "spam_twirling" for the Spam Twirling mitigation method, and "mitiq_zne" for the Zero-Noise Extrapolation (ZNE) mitigation method from Mitiq framework.
"""

def __init__(
self,
error_mitigation_technique: Optional[str] = None,
):
self.error_mitigation_technique = (
error_mitigation_technique.lower()
if type(error_mitigation_technique) == str
else error_mitigation_technique
)

class SpamProperties(ErrorMitigationProperties):
"""
Class containing all the required parameters for the execution of the SPAM twirling mitigation technique.
Parameters
----------
error_mitigation_technique: str
The name of the error mitigation technique. Only 'spam_twirling' supported for now.
The name of the error mitigation technique.
n_batches: Optional[int] = int
Number of batches in which the total number of shots is divided to. For every batch, we choose a set of qubits at random to which we apply X gates and classical negating. The dafault value is set to 10 to be comparable with most problem sizes in NISQ without creating too much of an overhead.
calibration_data_location: str
Expand All @@ -284,12 +303,8 @@ def __init__(
error_mitigation_technique: Optional[str] = None,
n_batches: Optional[int] = 10,
calibration_data_location: Optional[str] = None,
):
self.error_mitigation_technique = (
error_mitigation_technique.lower()
if type(error_mitigation_technique) == str
else error_mitigation_technique
)
):
super().__init__(error_mitigation_technique)

if isinstance(n_batches, int) and n_batches > 0:
self.n_batches = n_batches
Expand All @@ -307,7 +322,6 @@ def __init__(
"measurement_outcomes"
]
calibration_registers = calibration_data["register"]

except FileNotFoundError:
raise FileNotFoundError(
"Calibration data file not found at specified location: {}".format(
Expand All @@ -329,6 +343,45 @@ def __init__(

self.calibration_data_location = calibration_data_location

class MitiqZNEProperties(ErrorMitigationProperties):
"""
Class containing all the required parameters for the execution of the Mitiq Zero-Noise Extrapolation mitigation technique.
Parameters
----------
error_mitigation_technique: str
The name of the error mitigation technique.
factory: str
The name of the zero-noise extrapolation method. Supported values: "Richardson", "Linear", "Poly", "Exp", "PolyExp", "AdaExp", "FakeNodes".
scaling: str
The name of the function for scaling the noise of a quantum circuit. Supported values: "fold_gates_at_random", "fold_gates_from_right", "fold_gates_from_left".
scale_factors: List[int]
Sequence of noise scale factors at which expectation values should be measured.
For factory = "AdaExp", just the first element of the list will be considered.
order: int
Extrapolation order (degree of the polynomial fit). It cannot exceed len(scale_factors) - 1.
Only used for factory = "Poly" or "PolyExp".
steps: int
The number of optimization steps. At least 3 are necessary.
Only used for factory = "AdaExp".
"""

def __init__(
self,
error_mitigation_technique: Optional[str] = None,
factory: str = 'Linear',
scaling: str = 'fold_gates_at_random',
scale_factors: List[int] = [1,2,3],
order: int = 1,
steps: int = 4
):
super().__init__(error_mitigation_technique)
self.factory = factory
self.scaling = scaling
self.scale_factors = scale_factors
self.order = order
self.steps = steps


class ClassicalOptimizer(WorkflowProperties):
"""
Expand Down
Loading

0 comments on commit 7f84b11

Please sign in to comment.