Skip to content

Commit

Permalink
Allow to use QAOA-CV optimizer inside classification module (#312)
Browse files Browse the repository at this point in the history
* qaoa cv optimizer for QuanticNCH and MDM

* add QAOA-CV to classification module (MDM and NCH)

* complete tests

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* default create_mixer and n_reps for QAOA-CV

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* flake8

* add not implemented method for QAOA-CV

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* reshape solution

* add inequalitytoequality converter

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* encode not available in new version of the inegality converter.

* use LinearEqualityToPenalty converter instead

* - bound variables (even if type is binary -> fix bug with LinearEqualityToPenalty
- check if cost operator is pauli identity
- only one parameter in initial guess? (I am not sure why though)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* fix bug with quantum=False (private attribute _quantum_instance not available)

* add use_params to all mixers

* flake8

* simplify condition for checking whether there is quantum_instance

* missing condition on "cost operator has no parameters"

* missing file

* export is_pauli_identity to math module

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* add QAOACV test to light benchmark

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* flake8

* change default optimizer for QAOA-CV

* remove use_params misplaced

* Update pyriemann_qiskit/classification.py

Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>

* Update pyriemann_qiskit/classification.py

Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>

* Update pyriemann_qiskit/utils/math.py

Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>

* rename imports

* correct location of docstrings

* correct import of numpy

* Update pyriemann_qiskit/utils/docplex.py

Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>

* Update pyriemann_qiskit/utils/docplex.py

Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>

* replace all "our"

* replace reference to covariance in docplex module

* rename covmat to X

* check dostrings in docplex.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* Update pyriemann_qiskit/utils/docplex.py

Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>

* Update pyriemann_qiskit/utils/docplex.py

Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>

* Update pyriemann_qiskit/utils/docplex.py

Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>

* Update pyriemann_qiskit/utils/docplex.py

Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>

* Update pyriemann_qiskit/utils/docplex.py

Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>

* Update pyriemann_qiskit/utils/docplex.py

Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>

* Update pyriemann_qiskit/utils/docplex.py

Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>

* Update pyriemann_qiskit/utils/docplex.py

Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>

* Update pyriemann_qiskit/utils/hyper_params_factory.py

Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* Improve description of `docplex_spdmat`

* Update pyriemann_qiskit/utils/docplex.py

Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>

* fix import of light_benchmark

* Update light_benchmark.py

---------

Co-authored-by: Gregoire Cattan <gregoire.cattan@ibm.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Quentin Barthélemy <q.barthelemy@gmail.com>
  • Loading branch information
4 people authored Sep 30, 2024
1 parent cda6a00 commit 05c68c4
Show file tree
Hide file tree
Showing 11 changed files with 644 additions and 327 deletions.
49 changes: 36 additions & 13 deletions benchmarks/light_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,29 @@
# Modified from plot_classify_P300_bi.py of pyRiemann
# License: BSD (3-clause)

from pyriemann.estimation import XdawnCovariances, Shrinkage
from pyriemann.tangentspace import TangentSpace
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import balanced_accuracy_score
from sklearn.preprocessing import LabelEncoder
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.decomposition import PCA
import sys
import warnings

from moabb import set_log_level
from moabb.datasets import bi2012
from moabb.paradigms import P300
from pyriemann_qiskit.utils import distance, mean # noqa
from pyriemann.estimation import XdawnCovariances, Shrinkage
from pyriemann.tangentspace import TangentSpace
from pyriemann_qiskit.classification import QuanticNCH
from pyriemann_qiskit.pipelines import (
QuantumClassifierWithDefaultRiemannianPipeline,
QuantumMDMWithRiemannianPipeline,
)
from pyriemann_qiskit.classification import QuanticNCH
import warnings
import sys
from pyriemann_qiskit.utils import distance, mean # noqa
from pyriemann_qiskit.utils.hyper_params_factory import create_mixer_rotational_X_gates
from qiskit_algorithms.optimizers import SPSA
from sklearn.decomposition import PCA
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.metrics import balanced_accuracy_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import LabelEncoder


print(__doc__)

Expand Down Expand Up @@ -114,7 +118,6 @@
pipelines["NCH_MIN_HULL"] = make_pipeline(
XdawnCovariances(
nfilter=3,
# classes=[labels_dict["Target"]],
estimator="lwf",
xdawn_estimator="scm",
),
Expand All @@ -128,6 +131,26 @@
),
)


pipelines["NCH_MIN_HULL_QAOACV"] = make_pipeline(
XdawnCovariances(
nfilter=3,
estimator="lwf",
xdawn_estimator="scm",
),
QuanticNCH(
n_hulls_per_class=1,
n_samples_per_hull=3,
n_jobs=12,
subsampling="min",
quantum=True,
# Provide create_mixer to force QAOA-CV optimization
create_mixer=create_mixer_rotational_X_gates(0),
shots=100,
qaoa_optimizer=SPSA(maxiter=500),
),
)

##############################################################################
# Compute score
# --------------
Expand Down
2 changes: 2 additions & 0 deletions doc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ Hyper-parameters generation
create_mixer_rotational_X_gates
create_mixer_rotational_XY_gates
create_mixer_rotational_XZ_gates
create_mixer_qiskit_default

Preprocessing
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -169,6 +170,7 @@ Math
cov_to_corr_matrix
union_of_diff
to_xyz
is_pauli_identity

Firebase
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
20 changes: 10 additions & 10 deletions examples/toys_dataset/plot_qaoa_cv.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@

from docplex.mp.model import Model
import matplotlib.pyplot as plt
from qiskit.primitives import BackendSampler
from qiskit_aer import AerSimulator
from qiskit_algorithms.optimizers import COBYLA, SPSA

from pyriemann_qiskit.utils.docplex import QAOACVOptimizer
from pyriemann_qiskit.utils.hyper_params_factory import (
create_mixer_qiskit_default,
create_mixer_rotational_X_gates,
create_mixer_rotational_XY_gates,
create_mixer_rotational_XZ_gates,
Expand All @@ -46,16 +45,16 @@ def run_qaoa_cv(n_reps, optimizer, create_mixer):
# objective function to minimize
mdl.minimize((x - 0.83 + y + 2 * z) ** 2)

# Define the BackendSampler (previously QuantumInstance)
backend = AerSimulator(method="statevector", cuStateVec_enable=True)
quantum_instance = BackendSampler(
backend, options={"shots": 200, "seed_simulator": 42}
# Instanciate the QAOA-CV
# Note: if quantum_instance is None, it will be created inside the optimizer.
qaoa_cv = QAOACVOptimizer(
create_mixer, n_reps, quantum_instance=None, optimizer=optimizer
)
quantum_instance.transpile_options["seed_transpiler"] = 42

# Instanciate the QAOA-CV
qaoa_cv = QAOACVOptimizer(create_mixer, n_reps, quantum_instance, optimizer)
solution = qaoa_cv.solve(mdl)
# reshape is when working with covariance matrices
# So the vector of solution is reshaped into a matrix
# (this is not the case here)
solution = qaoa_cv.solve(mdl, reshape=False)

# print the time, the solution (that it the value for our three variable)
# and the minimum of the objective function
Expand Down Expand Up @@ -96,6 +95,7 @@ def run_qaoa_cv(n_reps, optimizer, create_mixer):
for angle in range(n_angles):
angle = math.pi * angle / n_angles
mixers = [
create_mixer_qiskit_default(angle),
create_mixer_rotational_X_gates(angle),
create_mixer_rotational_XY_gates(angle),
create_mixer_rotational_XZ_gates(angle),
Expand Down
111 changes: 88 additions & 23 deletions pyriemann_qiskit/classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
from .datasets import get_feature_dimension
from .utils.docplex import (
set_global_optimizer,
get_global_optimizer,
ClassicalOptimizer,
NaiveQAOAOptimizer,
QAOACVOptimizer,
)
from .utils.distance import (
distance_functions,
Expand Down Expand Up @@ -619,6 +621,37 @@ def parameter_count(self):
return 0


def _get_docplex_optimizer_from_params_bag(
logger,
quantum,
quantum_instance,
upper_bound,
qaoa_optimizer,
classical_optimizer,
create_mixer,
n_reps,
):
if quantum:
if create_mixer:
logger._log("Using QAOACVOptimizer")
return QAOACVOptimizer(
create_mixer=create_mixer,
n_reps=n_reps,
quantum_instance=quantum_instance,
optimizer=qaoa_optimizer,
)
else:
logger._log("Using NaiveQAOAOptimizer")
return NaiveQAOAOptimizer(
quantum_instance=quantum_instance,
upper_bound=upper_bound,
optimizer=qaoa_optimizer,
)
else:
logger._log("Using ClassicalOptimizer (COBYLA)")
return ClassicalOptimizer(classical_optimizer)


class QuanticMDM(QuanticClassifierBase):

"""Quantum-enhanced MDM classifier
Expand All @@ -639,6 +672,8 @@ class QuanticMDM(QuanticClassifierBase):
Add classical_optimizer parameter.
.. versionchanged:: 0.3.0
Add qaoa_optimizer parameter.
.. versionchanged:: 0.4.0
Add QAOA-CV optimization.
Parameters
----------
Expand Down Expand Up @@ -675,6 +710,13 @@ class QuanticMDM(QuanticClassifierBase):
qaoa_optimizer : SciPyOptimizer, default=SLSQP()
An instance of a scipy optimizer to find the optimal weights for the
parametric circuit (ansatz).
create_mixer : None | Callable[int, QuantumCircuit], default=None
A delegate that takes into input an angle and returns a QuantumCircuit.
This circuit is the mixer operatior for the QAOA-CV algorithm.
If None and quantum, the NaiveQAOAOptimizer will be used instead.
n_reps : int, default=3
The number of time the mixer and cost operator are repeated in the QAOA-CV
circuit.
See Also
--------
Expand Down Expand Up @@ -709,6 +751,8 @@ def __init__(
regularization=None,
classical_optimizer=CobylaOptimizer(rhobeg=2.1, rhoend=0.000001),
qaoa_optimizer=SLSQP(),
create_mixer=None,
n_reps=3,
):
QuanticClassifierBase.__init__(
self, quantum, q_account_token, verbose, shots, None, seed
Expand All @@ -718,6 +762,8 @@ def __init__(
self.regularization = regularization
self.classical_optimizer = classical_optimizer
self.qaoa_optimizer = qaoa_optimizer
self.create_mixer = create_mixer
self.n_reps = n_reps

@staticmethod
def _override_predict_distance(mdm):
Expand Down Expand Up @@ -753,16 +799,16 @@ def _init_algo(self, n_features):
classifier._predict_distances = QuanticMDM._override_predict_distance(
classifier
)
if self.quantum:
self._log("Using NaiveQAOAOptimizer")
self._optimizer = NaiveQAOAOptimizer(
quantum_instance=self._quantum_instance,
upper_bound=self.upper_bound,
optimizer=self.qaoa_optimizer,
)
else:
self._log("Using ClassicalOptimizer (COBYLA)")
self._optimizer = ClassicalOptimizer(self.classical_optimizer)
self._optimizer = _get_docplex_optimizer_from_params_bag(
self,
self.quantum,
self._quantum_instance if self.quantum else None,
self.upper_bound,
self.qaoa_optimizer,
self.classical_optimizer,
self.create_mixer,
self.n_reps,
)
set_global_optimizer(self._optimizer)
return classifier

Expand Down Expand Up @@ -953,9 +999,15 @@ def _predict_distances(self, X):
print("Not running in parallel")

if parallel:
dists = Parallel(n_jobs=self.n_jobs)(
delayed(self._process_sample)(x) for x in X
)
# Get global optimizer in this process
optimizer = get_global_optimizer(default=None)

def job(x):
# Set the global optimizer inside the new process
set_global_optimizer(optimizer)
return self._process_sample(x)

dists = Parallel(n_jobs=self.n_jobs)(delayed(job)(x) for x in X)

else:
for x in X:
Expand Down Expand Up @@ -1018,6 +1070,8 @@ class QuanticNCH(QuanticClassifierBase):
.. versionadded:: 0.2.0
.. versionchanged:: 0.3.0
Add qaoa_optimizer parameter.
.. versionchanged:: 0.4.0
Add QAOA-CV optimization.
Parameters
----------
Expand Down Expand Up @@ -1059,6 +1113,13 @@ class QuanticNCH(QuanticClassifierBase):
qaoa_optimizer : SciPyOptimizer, default=SLSQP()
An instance of a scipy optimizer to find the optimal weights for the
parametric circuit (ansatz).
create_mixer : None | Callable[int, QuantumCircuit], default=None
A delegate that takes into input an angle and returns a QuantumCircuit.
This circuit is the mixer operatior for the QAOA-CV algorithm.
If None and quantum, the NaiveQAOAOptimizer will be used instead.
n_reps : int, default=3
The number of time the mixer and cost operator are repeated in the QAOA-CV
circuit.
References
----------
Expand All @@ -1081,6 +1142,8 @@ def __init__(
n_samples_per_hull=10,
subsampling="min",
qaoa_optimizer=SLSQP(),
create_mixer=None,
n_reps=3,
):
QuanticClassifierBase.__init__(
self, quantum, q_account_token, verbose, shots, None, seed
Expand All @@ -1093,6 +1156,8 @@ def __init__(
self.n_jobs = n_jobs
self.subsampling = subsampling
self.qaoa_optimizer = qaoa_optimizer
self.create_mixer = create_mixer
self.n_reps = n_reps

def _init_algo(self, n_features):
self._log("Nearest Convex Hull Classifier initiating algorithm")
Expand All @@ -1104,16 +1169,16 @@ def _init_algo(self, n_features):
subsampling=self.subsampling,
)

if self.quantum:
self._log("Using NaiveQAOAOptimizer")
self._optimizer = NaiveQAOAOptimizer(
quantum_instance=self._quantum_instance,
upper_bound=self.upper_bound,
optimizer=self.qaoa_optimizer,
)
else:
self._log("Using ClassicalOptimizer")
self._optimizer = ClassicalOptimizer(self.classical_optimizer)
self._optimizer = _get_docplex_optimizer_from_params_bag(
self,
self.quantum,
self._quantum_instance if self.quantum else None,
self.upper_bound,
self.qaoa_optimizer,
self.classical_optimizer,
self.create_mixer,
self.n_reps,
)

# sets the optimizer for the distance functions
# used in NearestConvexHull class
Expand Down
Loading

0 comments on commit 05c68c4

Please sign in to comment.