From 9ecc93c4dc68988c4e777e81729deea8a48d169c Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Sun, 18 Feb 2024 15:40:19 +0100 Subject: [PATCH 01/90] - change `convex` by `cpm` (stands for constraint programming model) - add logeuclid mean - set default to cpm_le for QMDM --- benchmarks/light_benchmark.py | 4 +- doc/api.rst | 5 ++- examples/ERP/classify_P300_bi_quantum_mdm.py | 16 +++---- .../classify_alexmi_with_quantum_pipeline.py | 6 +-- pyriemann_qiskit/classification.py | 10 ++--- pyriemann_qiskit/pipelines.py | 40 ++++++++--------- pyriemann_qiskit/utils/__init__.py | 4 +- pyriemann_qiskit/utils/distance.py | 12 ++--- pyriemann_qiskit/utils/mean.py | 45 ++++++++++++++++--- tests/test_utils_distance.py | 8 ++-- tests/test_utils_mean.py | 30 ++++++------- 11 files changed, 108 insertions(+), 72 deletions(-) diff --git a/benchmarks/light_benchmark.py b/benchmarks/light_benchmark.py index 9f2a136b..e6ed28de 100644 --- a/benchmarks/light_benchmark.py +++ b/benchmarks/light_benchmark.py @@ -82,11 +82,11 @@ ) pipelines["QMDM_mean"] = QuantumMDMWithRiemannianPipeline( - convex_metric="mean", quantum=True + cpm_metric="mean", quantum=True ) pipelines["QMDM_dist"] = QuantumMDMWithRiemannianPipeline( - convex_metric="distance", quantum=True + cpm_metric="distance", quantum=True ) pipelines["RG_LDA"] = make_pipeline( diff --git a/doc/api.rst b/doc/api.rst index 71a05d71..988d59e8 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -96,7 +96,8 @@ Mean .. autosummary:: :toctree: generated/ - fro_mean_convex + fro_mean_cpm + le_mean_cpm Distance ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -106,7 +107,7 @@ Distance .. autosummary:: :toctree: generated/ - logeucl_dist_convex + logeucl_dist_cpm Docplex ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/examples/ERP/classify_P300_bi_quantum_mdm.py b/examples/ERP/classify_P300_bi_quantum_mdm.py index 60a50260..8532f593 100644 --- a/examples/ERP/classify_P300_bi_quantum_mdm.py +++ b/examples/ERP/classify_P300_bi_quantum_mdm.py @@ -6,8 +6,8 @@ The mean and the distance in MDM algorithm are formulated as optimization problems. These optimization problems are translated to Qiskit using Docplex and additional glue code. These optimizations -are enabled when we use convex mean or convex distance. This is set -using the 'convex_metric' parameter of the QuantumMDMWithRiemannianPipeline. +are enabled when we use cpm mean or cpm distance. This is set +using the 'cpm_metric' parameter of the QuantumMDMWithRiemannianPipeline. Classification can be run either on emulation or real quantum computer. @@ -43,7 +43,7 @@ from moabb.evaluations import WithinSessionEvaluation from moabb.paradigms import P300 -# inject convex distance and mean to pyriemann (if not done already) +# inject cpm distance and mean to pyriemann (if not done already) from pyriemann_qiskit.utils import distance, mean # noqa from pyriemann_qiskit.pipelines import ( QuantumMDMVotingClassifier, @@ -107,15 +107,15 @@ pipelines = {} -pipelines["mean=convex/distance=euclid"] = QuantumMDMWithRiemannianPipeline( - convex_metric="mean", quantum=quantum +pipelines["mean=cpm/distance=euclid"] = QuantumMDMWithRiemannianPipeline( + cpm_metric="mean", quantum=quantum ) -pipelines["mean=logeuclid/distance=convex"] = QuantumMDMWithRiemannianPipeline( - convex_metric="distance", quantum=quantum +pipelines["mean=logeuclid/distance=cpm"] = QuantumMDMWithRiemannianPipeline( + cpm_metric="distance", quantum=quantum ) -pipelines["Voting convex"] = QuantumMDMVotingClassifier(quantum=quantum) +pipelines["Voting cpm"] = QuantumMDMVotingClassifier(quantum=quantum) ############################################################################## # Run evaluation diff --git a/examples/MI/classify_alexmi_with_quantum_pipeline.py b/examples/MI/classify_alexmi_with_quantum_pipeline.py index dbcb60c9..cb68a523 100644 --- a/examples/MI/classify_alexmi_with_quantum_pipeline.py +++ b/examples/MI/classify_alexmi_with_quantum_pipeline.py @@ -20,7 +20,7 @@ from moabb.evaluations import WithinSessionEvaluation from moabb.paradigms import MotorImagery -# inject convex distance and mean to pyriemann (if not done already) +# inject cpm distance and mean to pyriemann (if not done already) from pyriemann_qiskit.utils import distance, mean # noqa from pyriemann_qiskit.pipelines import ( QuantumMDMWithRiemannianPipeline, @@ -68,8 +68,8 @@ pipelines = {} # Will run QAOA under the hood -pipelines["mean=logeuclid/distance=convex"] = QuantumMDMWithRiemannianPipeline( - convex_metric="distance", quantum=True +pipelines["mean=logeuclid/distance=cpm"] = QuantumMDMWithRiemannianPipeline( + cpm_metric="distance", quantum=True ) # Classical baseline for evaluation diff --git a/pyriemann_qiskit/classification.py b/pyriemann_qiskit/classification.py index 8a9b6f23..d78950fa 100644 --- a/pyriemann_qiskit/classification.py +++ b/pyriemann_qiskit/classification.py @@ -582,7 +582,7 @@ class QuanticMDM(QuanticClassifierBase): """Quantum-enhanced MDM classifier - This class is a convex implementation of the Minimum Distance to Mean (MDM) + This class is a cpm implementation of the Minimum Distance to Mean (MDM) [1]_, which can run with quantum optimization. Only log-Euclidean distance between trial and class prototypes is supported at the moment, but any type of metric can be used for centroid estimation. @@ -597,7 +597,7 @@ class QuanticMDM(QuanticClassifierBase): Parameters ---------- - metric : string | dict, default={"mean": 'logeuclid', "distance": 'convex'} + metric : string | dict, default={"mean": 'logeuclid', "distance": 'cpm'} The type of metric used for centroid and distance estimation. see `mean_covariance` for the list of supported metric. the metric could be a dict with two keys, `mean` and `distance` in @@ -606,7 +606,7 @@ class QuanticMDM(QuanticClassifierBase): the mean in order to boost the computional speed and 'riemann' for the distance in order to keep the good sensitivity for the classification. quantum : bool (default: True) - Only applies if `metric` contains a convex distance or mean. + Only applies if `metric` contains a cpm distance or mean. - If true will run on local or remote backend (depending on q_account_token value), @@ -646,7 +646,7 @@ class QuanticMDM(QuanticClassifierBase): def __init__( self, - metric={"mean": "logeuclid", "distance": "convex"}, + metric={"mean": "logeuclid", "distance": "cpm_le"}, quantum=True, q_account_token=None, verbose=True, @@ -661,7 +661,7 @@ def __init__( self.upper_bound = upper_bound def _init_algo(self, n_features): - self._log("Convex MDM initiating algorithm") + self._log("cpm MDM initiating algorithm") classifier = MDM(metric=self.metric) if self.quantum: self._log("Using NaiveQAOAOptimizer") diff --git a/pyriemann_qiskit/pipelines.py b/pyriemann_qiskit/pipelines.py index 56c1c5d0..b03b6679 100644 --- a/pyriemann_qiskit/pipelines.py +++ b/pyriemann_qiskit/pipelines.py @@ -304,19 +304,19 @@ def _create_pipe(self): class QuantumMDMWithRiemannianPipeline(BasePipeline): - """MDM with Riemannian pipeline adapted for convex metrics. + """MDM with Riemannian pipeline adapted for cpm metrics. It can run on classical or quantum optimizer. Parameters ---------- - convex_metric : string (default: "distance") + cpm_metric : string (default: "distance") `metric` passed to the inner QuanticMDM depends on the - `convex_metric` as follows (convex_metric => metric): + `cpm_metric` as follows (cpm_metric => metric): - - "distance" => {mean=logeuclid, distance=convex}, - - "mean" => {mean=convex, distance=euclid}, - - "both" => {mean=convex, distance=convex}, + - "distance" => {mean=logeuclid, distance=cpm}, + - "mean" => {mean=cpm, distance=euclid}, + - "both" => {mean=cpm, distance=cpm}, - other => same as "distance". quantum : bool (default: True) - If true will run on local or remote backend @@ -351,14 +351,14 @@ class QuantumMDMWithRiemannianPipeline(BasePipeline): def __init__( self, - convex_metric="distance", + cpm_metric="distance", quantum=True, q_account_token=None, verbose=True, shots=1024, upper_bound=7, ): - self.convex_metric = convex_metric + self.cpm_metric = cpm_metric self.quantum = quantum self.q_account_token = q_account_token self.verbose = verbose @@ -368,14 +368,14 @@ def __init__( BasePipeline.__init__(self, "QuantumMDMWithRiemannianPipeline") def _create_pipe(self): - if self.convex_metric == "both": - metric = {"mean": "convex", "distance": "convex"} - elif self.convex_metric == "mean": - metric = {"mean": "convex", "distance": "euclid"} + if self.cpm_metric == "both": + metric = {"mean": "cpm_le", "distance": "cpm_le"} + elif self.cpm_metric == "mean": + metric = {"mean": "cpm_le", "distance": "logeuclid"} else: - metric = {"mean": "logeuclid", "distance": "convex"} + metric = {"mean": "logeuclid", "distance": "cpm_le"} - if metric["mean"] == "convex": + if metric["mean"] == "cpm_le": if self.quantum: covariances = XdawnCovariances( nfilter=1, estimator="scm", xdawn_estimator="lwf" @@ -407,8 +407,8 @@ class QuantumMDMVotingClassifier(BasePipeline): Voting classifier with two configurations of QuantumMDMWithRiemannianPipeline: - - with mean = convex and distance = euclid, - - with mean = logeuclid and distance = convex. + - with mean = cpm and distance = euclid, + - with mean = logeuclid and distance = cpm. Parameters ---------- @@ -460,7 +460,7 @@ def __init__( BasePipeline.__init__(self, "QuantumMDMVotingClassifier") def _create_pipe(self): - clf_mean_logeuclid_dist_convex = QuantumMDMWithRiemannianPipeline( + clf_mean_logeuclid_dist_cpm = QuantumMDMWithRiemannianPipeline( "distance", self.quantum, self.q_account_token, @@ -468,7 +468,7 @@ def _create_pipe(self): self.shots, self.upper_bound, ) - clf_mean_convex_dist_euclid = QuantumMDMWithRiemannianPipeline( + clf_mean_cpm_dist_euclid = QuantumMDMWithRiemannianPipeline( "mean", self.quantum, self.q_account_token, @@ -480,8 +480,8 @@ def _create_pipe(self): return make_pipeline( VotingClassifier( [ - ("mean_logeuclid_dist_convex", clf_mean_logeuclid_dist_convex), - ("mean_convex_dist_euclid ", clf_mean_convex_dist_euclid), + ("mean_logeuclid_dist_cpm", clf_mean_logeuclid_dist_cpm), + ("mean_cpm_dist_euclid ", clf_mean_cpm_dist_euclid), ], voting="soft", ) diff --git a/pyriemann_qiskit/utils/__init__.py b/pyriemann_qiskit/utils/__init__.py index c44c297b..f55e99c2 100644 --- a/pyriemann_qiskit/utils/__init__.py +++ b/pyriemann_qiskit/utils/__init__.py @@ -18,7 +18,7 @@ add_moabb_dataframe_results_to_caches, convert_caches_to_dataframes, ) -from .distance import logeucl_dist_convex +from .distance import logeucl_dist_cpm __all__ = [ "hyper_params_factory", @@ -36,7 +36,7 @@ "NaiveQAOAOptimizer", "set_global_optimizer", "get_global_optimizer", - "logeucl_dist_convex", + "logeucl_dist_cpm", "FirebaseConnector", "Cache", "generate_caches", diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index cc1970dc..30a6cebd 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -6,8 +6,8 @@ from pyriemann.utils.base import logm -def logeucl_dist_convex(X, y, optimizer=ClassicalOptimizer()): - """Convex formulation of the MDM algorithm with log-Euclidean metric. +def logeucl_dist_cpm(X, y, optimizer=ClassicalOptimizer()): + """Constraint Programming Model (CPM) formulation of the MDM algorithm with log-Euclidean metric. Parameters ---------- @@ -67,9 +67,9 @@ def log_prod(m1, m2): def predict_distances(mdm, X): - if mdm.metric_dist == "convex": + if mdm.metric_dist == "cpm_le": centroids = np.array(mdm.covmeans_) - return np.array([logeucl_dist_convex(centroids, x) for x in X]) + return np.array([logeucl_dist_cpm(centroids, x) for x in X]) else: return _mdm_predict_distances_original(mdm, X) @@ -78,7 +78,7 @@ def predict_distances(mdm, X): # This is only for validation inside the MDM. # In fact, we override the _predict_distances method -# inside MDM to directly use logeucl_dist_convex when the metric is "convex" +# inside MDM to directly use logeucl_dist_cpm when the metric is "cpm_le" # This is due to the fact the the signature of this method is different from # the usual distance functions. -distance_functions["convex"] = logeucl_dist_convex +distance_functions["cpm_le"] = logeucl_dist_cpm diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 15cdfb3c..95b30a10 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -2,12 +2,13 @@ from pyriemann.utils.mean import mean_functions from pyriemann_qiskit.utils.docplex import ClassicalOptimizer, get_global_optimizer from pyriemann.estimation import Shrinkage +from pyriemann.utils.base import logm, expm +import numpy as np - -def fro_mean_convex( +def fro_mean_cpm( covmats, sample_weight=None, optimizer=ClassicalOptimizer(), shrink=True ): - """Convex formulation of the mean with Frobenius distance. + """Constraint Programm Model (CPM) formulation of the mean with Frobenius distance. Parameters ---------- @@ -25,7 +26,7 @@ def fro_mean_convex( Returns ------- mean : ndarray, shape (n_channels, n_channels) - Convex-optimized Frobenius mean. + CPM-optimized Frobenius mean. Notes ----- @@ -66,4 +67,38 @@ def _fro_dist(A, B): return result -mean_functions["convex"] = fro_mean_convex +def le_mean_cpm( + covmats, sample_weight=None, optimizer=ClassicalOptimizer(), shrink=True +): + """Constraint Programm Model (CPM) formulation of the mean with log-euclidian distance. + + Parameters + ---------- + covmats: ndarray, shape (n_matrices, n_channels, n_channels) + Set of SPD matrices. + sample_weights: None | ndarray, shape (n_matrices,), default=None + Weights for each matrix. Never used in practice. + It is kept only for standardization with pyRiemann. + optimizer: pyQiskitOptimizer + An instance of pyQiskitOptimizer. + shrink: boolean (default: true) + If True, it applies shrinkage regularization [2]_ + of the resulting covariance matrix. + + Returns + ------- + mean : ndarray, shape (n_channels, n_channels) + CPM-optimized Frobenius mean. + + Notes + ----- + .. versionadded:: 0.2.0 + + """ + + log_covmats = np.array(logm(covmat) for covmat in covmats) + result = fro_mean_cpm(log_covmats, sample_weight, optimizer, shrink) + return expm(result) + +mean_functions["cpm_fro"] = fro_mean_cpm +mean_functions["cpm_le"] = le_mean_cpm \ No newline at end of file diff --git a/tests/test_utils_distance.py b/tests/test_utils_distance.py index e59f4ddc..d711a497 100644 --- a/tests/test_utils_distance.py +++ b/tests/test_utils_distance.py @@ -3,7 +3,7 @@ from pyriemann_qiskit.utils import ( ClassicalOptimizer, NaiveQAOAOptimizer, - logeucl_dist_convex, + logeucl_dist_cpm, ) from pyriemann_qiskit.datasets import get_mne_sample from pyriemann.classification import MDM @@ -13,7 +13,7 @@ def test_performance(): - metric = {"mean": "logeuclid", "distance": "convex"} + metric = {"mean": "logeuclid", "distance": "cpm_le"} clf = make_pipeline(XdawnCovariances(), MDM(metric=metric)) skf = StratifiedKFold(n_splits=3) @@ -23,10 +23,10 @@ def test_performance(): @pytest.mark.parametrize("optimizer", [ClassicalOptimizer(), NaiveQAOAOptimizer()]) -def test_logeucl_dist_convex(optimizer): +def test_logeucl_dist_cpm(optimizer): X_0 = np.array([[0.9, 1.1], [0.9, 1.1]]) X_1 = X_0 + 1 X = np.stack((X_0, X_1)) y = (X_0 + X_1) / 3 - distances = logeucl_dist_convex(X, y, optimizer=optimizer) + distances = logeucl_dist_cpm(X, y, optimizer=optimizer) assert distances.argmin() == 0 diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index 4fe1ac52..5c294c44 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -5,12 +5,12 @@ from pyriemann.estimation import XdawnCovariances from sklearn.pipeline import make_pipeline from sklearn.model_selection import StratifiedKFold, cross_val_score -from pyriemann_qiskit.utils.mean import fro_mean_convex +from pyriemann_qiskit.utils.mean import fro_mean_cpm from pyriemann_qiskit.utils import ClassicalOptimizer, NaiveQAOAOptimizer def test_performance(get_covmats, get_labels): - metric = {"mean": "convex", "distance": "euclid"} + metric = {"mean": "cpm_le", "distance": "euclid"} clf = make_pipeline(XdawnCovariances(), MDM(metric=metric)) skf = StratifiedKFold(n_splits=5) @@ -22,57 +22,57 @@ def test_performance(get_covmats, get_labels): assert score.mean() > 0 -def test_mean_convex_vs_euclid(get_covmats): - """Test that euclidian and convex mean returns close results""" +def test_mean_cpm_vs_euclid(get_covmats): + """Test that euclidian and cpm mean returns close results""" n_trials, n_channels = 5, 3 covmats = get_covmats(n_trials, n_channels) - C = fro_mean_convex(covmats, shrink=False) + C = fro_mean_cpm(covmats, shrink=False) C_euclid = mean_euclid(covmats) assert np.allclose(C, C_euclid, atol=0.001) -def test_mean_convex_shape(get_covmats): +def test_mean_cpm_shape(get_covmats): """Test the shape of mean""" n_trials, n_channels = 5, 3 covmats = get_covmats(n_trials, n_channels) - C = fro_mean_convex(covmats) + C = fro_mean_cpm(covmats) assert C.shape == (n_channels, n_channels) @pytest.mark.parametrize("optimizer", [ClassicalOptimizer(), NaiveQAOAOptimizer()]) -def test_mean_convex_all_zeros(optimizer): +def test_mean_cpm_all_zeros(optimizer): """Test that the mean of covariance matrices containing zeros is a matrix filled with zeros""" n_trials, n_channels = 5, 2 covmats = np.zeros((n_trials, n_channels, n_channels)) - C = fro_mean_convex(covmats, optimizer=optimizer, shrink=False) + C = fro_mean_cpm(covmats, optimizer=optimizer, shrink=False) assert np.allclose(covmats[0], C, atol=0.001) -def test_mean_convex_all_ones(): +def test_mean_cpm_all_ones(): """Test that the mean of covariance matrices containing ones is a matrix filled with ones""" n_trials, n_channels = 5, 2 covmats = np.ones((n_trials, n_channels, n_channels)) - C = fro_mean_convex(covmats, shrink=False) + C = fro_mean_cpm(covmats, shrink=False) assert np.allclose(covmats[0], C, atol=0.001) -def test_mean_convex_all_equals(): +def test_mean_cpm_all_equals(): """Test that the mean of covariance matrices filled with the same value is a matrix identical to the input""" n_trials, n_channels, value = 5, 2, 2.5 covmats = np.full((n_trials, n_channels, n_channels), value) - C = fro_mean_convex(covmats, shrink=False) + C = fro_mean_cpm(covmats, shrink=False) assert np.allclose(covmats[0], C, atol=0.001) -def test_mean_convex_mixed(): +def test_mean_cpm_mixed(): """Test that the mean of covariances matrices with zero and ones is a matrix filled with 0.5""" n_trials, n_channels = 5, 2 covmats_0 = np.zeros((n_trials, n_channels, n_channels)) covmats_1 = np.ones((n_trials, n_channels, n_channels)) expected_mean = np.full((n_channels, n_channels), 0.5) - C = fro_mean_convex(np.concatenate((covmats_0, covmats_1), axis=0), shrink=False) + C = fro_mean_cpm(np.concatenate((covmats_0, covmats_1), axis=0), shrink=False) assert np.allclose(expected_mean, C, atol=0.001) From f65c4b211b70fed9879a6b76284b72287899336b Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Sun, 18 Feb 2024 15:58:25 +0100 Subject: [PATCH 02/90] - parametetrize tests - fix bug in mean.py --- pyriemann_qiskit/utils/mean.py | 2 +- tests/test_utils_mean.py | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 95b30a10..59cde4e7 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -96,7 +96,7 @@ def le_mean_cpm( """ - log_covmats = np.array(logm(covmat) for covmat in covmats) + log_covmats = logm(covmats) result = fro_mean_cpm(log_covmats, sample_weight, optimizer, shrink) return expm(result) diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index 5c294c44..1cf47184 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -1,16 +1,16 @@ import pytest import numpy as np -from pyriemann.utils.mean import mean_euclid +from pyriemann.utils.mean import mean_euclid, mean_logeuclid from pyriemann.classification import MDM from pyriemann.estimation import XdawnCovariances from sklearn.pipeline import make_pipeline from sklearn.model_selection import StratifiedKFold, cross_val_score -from pyriemann_qiskit.utils.mean import fro_mean_cpm +from pyriemann_qiskit.utils.mean import fro_mean_cpm, le_mean_cpm from pyriemann_qiskit.utils import ClassicalOptimizer, NaiveQAOAOptimizer def test_performance(get_covmats, get_labels): - metric = {"mean": "cpm_le", "distance": "euclid"} + metric = {"mean": "cpm_fro", "distance": "euclid"} clf = make_pipeline(XdawnCovariances(), MDM(metric=metric)) skf = StratifiedKFold(n_splits=5) @@ -22,14 +22,17 @@ def test_performance(get_covmats, get_labels): assert score.mean() > 0 -def test_mean_cpm_vs_euclid(get_covmats): - """Test that euclidian and cpm mean returns close results""" +@pytest.mark.parametrize("means", + [(mean_euclid, fro_mean_cpm), + (mean_logeuclid, le_mean_cpm)]) +def test_analytic_vs_cpm_mean(get_covmats, means): + """Test that analytic and cpm mean returns close results""" + analytic_mean, cpm_mean = means n_trials, n_channels = 5, 3 covmats = get_covmats(n_trials, n_channels) - C = fro_mean_cpm(covmats, shrink=False) - C_euclid = mean_euclid(covmats) - assert np.allclose(C, C_euclid, atol=0.001) - + C = cpm_mean(covmats, shrink=False) + C_analytic = analytic_mean(covmats) + assert np.allclose(C, C_analytic, atol=0.001) def test_mean_cpm_shape(get_covmats): """Test the shape of mean""" From fc4a3237aafc02185eb69f389878469d7b6ba70f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 18 Feb 2024 14:59:17 +0000 Subject: [PATCH 03/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- pyriemann_qiskit/utils/mean.py | 4 +++- tests/test_utils_mean.py | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 59cde4e7..50c63c82 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -5,6 +5,7 @@ from pyriemann.utils.base import logm, expm import numpy as np + def fro_mean_cpm( covmats, sample_weight=None, optimizer=ClassicalOptimizer(), shrink=True ): @@ -100,5 +101,6 @@ def le_mean_cpm( result = fro_mean_cpm(log_covmats, sample_weight, optimizer, shrink) return expm(result) + mean_functions["cpm_fro"] = fro_mean_cpm -mean_functions["cpm_le"] = le_mean_cpm \ No newline at end of file +mean_functions["cpm_le"] = le_mean_cpm diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index 1cf47184..70044d88 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -22,9 +22,9 @@ def test_performance(get_covmats, get_labels): assert score.mean() > 0 -@pytest.mark.parametrize("means", - [(mean_euclid, fro_mean_cpm), - (mean_logeuclid, le_mean_cpm)]) +@pytest.mark.parametrize( + "means", [(mean_euclid, fro_mean_cpm), (mean_logeuclid, le_mean_cpm)] +) def test_analytic_vs_cpm_mean(get_covmats, means): """Test that analytic and cpm mean returns close results""" analytic_mean, cpm_mean = means @@ -34,6 +34,7 @@ def test_analytic_vs_cpm_mean(get_covmats, means): C_analytic = analytic_mean(covmats) assert np.allclose(C, C_analytic, atol=0.001) + def test_mean_cpm_shape(get_covmats): """Test the shape of mean""" n_trials, n_channels = 5, 3 From 3a1eff994a82f8b73a97ecd6ff136e3f8e3b81d0 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Sun, 18 Feb 2024 16:35:15 +0100 Subject: [PATCH 04/90] fix qiskit version --- doc/requirements.txt | 1 + requirements.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/requirements.txt b/doc/requirements.txt index 5639a962..7545fe2c 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -10,6 +10,7 @@ imbalanced-learn==0.11.0 joblib pandas cvxpy==1.4.1 +qiskit==0.45.0 qiskit_machine_learning==0.6.1 qiskit-ibm-provider==0.7.3 qiskit-optimization==0.5.0 diff --git a/requirements.txt b/requirements.txt index bc45cb22..02c17745 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ numpy<1.27 cython scikit-learn==1.3.2 git+https://github.com/pyRiemann/pyRiemann#egg=pyriemann +qiskit==0.45.0 qiskit_machine_learning==0.6.1 qiskit-ibm-provider==0.7.3 qiskit-optimization==0.5.0 From 38d4ca9f86fd1fa06867656ca03070a7624f68d2 Mon Sep 17 00:00:00 2001 From: gcattan Date: Mon, 19 Feb 2024 22:09:27 +0100 Subject: [PATCH 05/90] Update doc/api.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- doc/api.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api.rst b/doc/api.rst index 988d59e8..fa3fc887 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -96,8 +96,8 @@ Mean .. autosummary:: :toctree: generated/ - fro_mean_cpm - le_mean_cpm + mean_euclid_cpm + mean_logeuclid_cpm Distance ~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 8f657ead14d9a1757461979b2d9175b3b0aed973 Mon Sep 17 00:00:00 2001 From: gcattan Date: Mon, 19 Feb 2024 22:16:08 +0100 Subject: [PATCH 06/90] Update pyriemann_qiskit/utils/mean.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/mean.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 50c63c82..37319e12 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -6,6 +6,13 @@ import numpy as np +@deprecated( + "fro_mean_convex is deprecated and will be removed in 0.3.0; " + "please use fro_mean_cpm." +) +def fro_mean_convex(): + pass + def fro_mean_cpm( covmats, sample_weight=None, optimizer=ClassicalOptimizer(), shrink=True ): From c396820f6df485df48dd1c1d2f8d89a4a38ce3be Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 21:16:14 +0000 Subject: [PATCH 07/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- pyriemann_qiskit/utils/mean.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 37319e12..e33d6e19 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -13,6 +13,7 @@ def fro_mean_convex(): pass + def fro_mean_cpm( covmats, sample_weight=None, optimizer=ClassicalOptimizer(), shrink=True ): From fcff07985c43448f63da72713bc4c084dc4527d3 Mon Sep 17 00:00:00 2001 From: gcattan Date: Mon, 19 Feb 2024 22:19:42 +0100 Subject: [PATCH 08/90] Update pyriemann_qiskit/utils/mean.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/mean.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index e33d6e19..14eadac7 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -97,7 +97,7 @@ def le_mean_cpm( Returns ------- mean : ndarray, shape (n_channels, n_channels) - CPM-optimized Frobenius mean. + CPM-optimized Log-Euclidean mean. Notes ----- From 9be8e984cf42be6c2a88b87bcc1098f376f3a41d Mon Sep 17 00:00:00 2001 From: gcattan Date: Mon, 19 Feb 2024 22:21:18 +0100 Subject: [PATCH 09/90] Update pyriemann_qiskit/utils/mean.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/mean.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 14eadac7..67ecec68 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -35,7 +35,7 @@ def fro_mean_cpm( Returns ------- mean : ndarray, shape (n_channels, n_channels) - CPM-optimized Frobenius mean. + CPM-optimized Euclidean mean. Notes ----- From 99a130a3fdb26b269ffa049c8217af51c7f3461c Mon Sep 17 00:00:00 2001 From: gcattan Date: Mon, 19 Feb 2024 22:24:28 +0100 Subject: [PATCH 10/90] Update pyriemann_qiskit/classification.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/classification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyriemann_qiskit/classification.py b/pyriemann_qiskit/classification.py index d78950fa..cfe434da 100644 --- a/pyriemann_qiskit/classification.py +++ b/pyriemann_qiskit/classification.py @@ -582,7 +582,7 @@ class QuanticMDM(QuanticClassifierBase): """Quantum-enhanced MDM classifier - This class is a cpm implementation of the Minimum Distance to Mean (MDM) + This class is a quantic implementation of the Minimum Distance to Mean (MDM) [1]_, which can run with quantum optimization. Only log-Euclidean distance between trial and class prototypes is supported at the moment, but any type of metric can be used for centroid estimation. From e82d4046f431f75720fc729052b11a581f61fd26 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Mon, 19 Feb 2024 22:49:45 +0100 Subject: [PATCH 11/90] - Rename cpm to cpm-le in some places - add import for @deprecated --- examples/ERP/classify_P300_bi_quantum_mdm.py | 6 +++--- pyriemann_qiskit/classification.py | 2 +- pyriemann_qiskit/pipelines.py | 6 +++--- pyriemann_qiskit/utils/mean.py | 1 + 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/ERP/classify_P300_bi_quantum_mdm.py b/examples/ERP/classify_P300_bi_quantum_mdm.py index 8532f593..0d889c1b 100644 --- a/examples/ERP/classify_P300_bi_quantum_mdm.py +++ b/examples/ERP/classify_P300_bi_quantum_mdm.py @@ -107,15 +107,15 @@ pipelines = {} -pipelines["mean=cpm/distance=euclid"] = QuantumMDMWithRiemannianPipeline( +pipelines["mean=cpm-le/distance=logeuclid"] = QuantumMDMWithRiemannianPipeline( cpm_metric="mean", quantum=quantum ) -pipelines["mean=logeuclid/distance=cpm"] = QuantumMDMWithRiemannianPipeline( +pipelines["mean=logeuclid/distance=cpm-le"] = QuantumMDMWithRiemannianPipeline( cpm_metric="distance", quantum=quantum ) -pipelines["Voting cpm"] = QuantumMDMVotingClassifier(quantum=quantum) +pipelines["Voting cpm-le"] = QuantumMDMVotingClassifier(quantum=quantum) ############################################################################## # Run evaluation diff --git a/pyriemann_qiskit/classification.py b/pyriemann_qiskit/classification.py index cfe434da..d1a917c4 100644 --- a/pyriemann_qiskit/classification.py +++ b/pyriemann_qiskit/classification.py @@ -661,7 +661,7 @@ def __init__( self.upper_bound = upper_bound def _init_algo(self, n_features): - self._log("cpm MDM initiating algorithm") + self._log("Quantic MDM initiating algorithm") classifier = MDM(metric=self.metric) if self.quantum: self._log("Using NaiveQAOAOptimizer") diff --git a/pyriemann_qiskit/pipelines.py b/pyriemann_qiskit/pipelines.py index b03b6679..a6403013 100644 --- a/pyriemann_qiskit/pipelines.py +++ b/pyriemann_qiskit/pipelines.py @@ -314,9 +314,9 @@ class QuantumMDMWithRiemannianPipeline(BasePipeline): `metric` passed to the inner QuanticMDM depends on the `cpm_metric` as follows (cpm_metric => metric): - - "distance" => {mean=logeuclid, distance=cpm}, - - "mean" => {mean=cpm, distance=euclid}, - - "both" => {mean=cpm, distance=cpm}, + - "distance" => {mean=logeuclid, distance=cpm-le}, + - "mean" => {mean=cpm-le, distance=logeuclid}, + - "both" => {mean=cpm-le, distance=cpm-le}, - other => same as "distance". quantum : bool (default: True) - If true will run on local or remote backend diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 67ecec68..16cc3ff0 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -1,3 +1,4 @@ +from typing_extensions import deprecated from docplex.mp.model import Model from pyriemann.utils.mean import mean_functions from pyriemann_qiskit.utils.docplex import ClassicalOptimizer, get_global_optimizer From 650e98734f2fa99d82773d6e1f9df3feeff0005f Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Mon, 19 Feb 2024 22:54:51 +0100 Subject: [PATCH 12/90] rename fro_mean_cpm -> mean_euclid_cpm rename le_mean_cpm -> mean_logeuclid_cpm --- pyriemann_qiskit/utils/mean.py | 12 ++++++------ tests/test_utils_mean.py | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 16cc3ff0..37fa1e84 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -9,13 +9,13 @@ @deprecated( "fro_mean_convex is deprecated and will be removed in 0.3.0; " - "please use fro_mean_cpm." + "please use mean_euclid_cpm." ) def fro_mean_convex(): pass -def fro_mean_cpm( +def mean_euclid_cpm( covmats, sample_weight=None, optimizer=ClassicalOptimizer(), shrink=True ): """Constraint Programm Model (CPM) formulation of the mean with Frobenius distance. @@ -77,7 +77,7 @@ def _fro_dist(A, B): return result -def le_mean_cpm( +def mean_logeuclid_cpm( covmats, sample_weight=None, optimizer=ClassicalOptimizer(), shrink=True ): """Constraint Programm Model (CPM) formulation of the mean with log-euclidian distance. @@ -107,9 +107,9 @@ def le_mean_cpm( """ log_covmats = logm(covmats) - result = fro_mean_cpm(log_covmats, sample_weight, optimizer, shrink) + result = mean_euclid_cpm(log_covmats, sample_weight, optimizer, shrink) return expm(result) -mean_functions["cpm_fro"] = fro_mean_cpm -mean_functions["cpm_le"] = le_mean_cpm +mean_functions["cpm_fro"] = mean_euclid_cpm +mean_functions["cpm_le"] = mean_logeuclid_cpm diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index 70044d88..3545062e 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -5,7 +5,7 @@ from pyriemann.estimation import XdawnCovariances from sklearn.pipeline import make_pipeline from sklearn.model_selection import StratifiedKFold, cross_val_score -from pyriemann_qiskit.utils.mean import fro_mean_cpm, le_mean_cpm +from pyriemann_qiskit.utils.mean import mean_euclid_cpm, mean_logeuclid_cpm from pyriemann_qiskit.utils import ClassicalOptimizer, NaiveQAOAOptimizer @@ -23,7 +23,7 @@ def test_performance(get_covmats, get_labels): @pytest.mark.parametrize( - "means", [(mean_euclid, fro_mean_cpm), (mean_logeuclid, le_mean_cpm)] + "means", [(mean_euclid, mean_euclid_cpm), (mean_logeuclid, mean_logeuclid_cpm)] ) def test_analytic_vs_cpm_mean(get_covmats, means): """Test that analytic and cpm mean returns close results""" @@ -39,7 +39,7 @@ def test_mean_cpm_shape(get_covmats): """Test the shape of mean""" n_trials, n_channels = 5, 3 covmats = get_covmats(n_trials, n_channels) - C = fro_mean_cpm(covmats) + C = mean_euclid_cpm(covmats) assert C.shape == (n_channels, n_channels) @@ -49,7 +49,7 @@ def test_mean_cpm_all_zeros(optimizer): is a matrix filled with zeros""" n_trials, n_channels = 5, 2 covmats = np.zeros((n_trials, n_channels, n_channels)) - C = fro_mean_cpm(covmats, optimizer=optimizer, shrink=False) + C = mean_euclid_cpm(covmats, optimizer=optimizer, shrink=False) assert np.allclose(covmats[0], C, atol=0.001) @@ -58,7 +58,7 @@ def test_mean_cpm_all_ones(): is a matrix filled with ones""" n_trials, n_channels = 5, 2 covmats = np.ones((n_trials, n_channels, n_channels)) - C = fro_mean_cpm(covmats, shrink=False) + C = mean_euclid_cpm(covmats, shrink=False) assert np.allclose(covmats[0], C, atol=0.001) @@ -67,7 +67,7 @@ def test_mean_cpm_all_equals(): is a matrix identical to the input""" n_trials, n_channels, value = 5, 2, 2.5 covmats = np.full((n_trials, n_channels, n_channels), value) - C = fro_mean_cpm(covmats, shrink=False) + C = mean_euclid_cpm(covmats, shrink=False) assert np.allclose(covmats[0], C, atol=0.001) @@ -78,5 +78,5 @@ def test_mean_cpm_mixed(): covmats_0 = np.zeros((n_trials, n_channels, n_channels)) covmats_1 = np.ones((n_trials, n_channels, n_channels)) expected_mean = np.full((n_channels, n_channels), 0.5) - C = fro_mean_cpm(np.concatenate((covmats_0, covmats_1), axis=0), shrink=False) + C = mean_euclid_cpm(np.concatenate((covmats_0, covmats_1), axis=0), shrink=False) assert np.allclose(expected_mean, C, atol=0.001) From 39d6395033108483a24f9618bc2c5c5b024a603e Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Mon, 19 Feb 2024 23:01:12 +0100 Subject: [PATCH 13/90] rename cpm_metric -> metric --- benchmarks/light_benchmark.py | 4 ++-- examples/ERP/classify_P300_bi_illiteracy.py | 6 +++--- examples/ERP/classify_P300_bi_quantum_mdm.py | 6 +++--- .../MI/classify_alexmi_with_quantum_pipeline.py | 2 +- pyriemann_qiskit/pipelines.py | 16 ++++++++-------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/benchmarks/light_benchmark.py b/benchmarks/light_benchmark.py index e6ed28de..171844da 100644 --- a/benchmarks/light_benchmark.py +++ b/benchmarks/light_benchmark.py @@ -82,11 +82,11 @@ ) pipelines["QMDM_mean"] = QuantumMDMWithRiemannianPipeline( - cpm_metric="mean", quantum=True + metric="mean", quantum=True ) pipelines["QMDM_dist"] = QuantumMDMWithRiemannianPipeline( - cpm_metric="distance", quantum=True + metric="distance", quantum=True ) pipelines["RG_LDA"] = make_pipeline( diff --git a/examples/ERP/classify_P300_bi_illiteracy.py b/examples/ERP/classify_P300_bi_illiteracy.py index 368ad6a6..5bde4378 100644 --- a/examples/ERP/classify_P300_bi_illiteracy.py +++ b/examples/ERP/classify_P300_bi_illiteracy.py @@ -172,19 +172,19 @@ def placeholder(key): placeholder(PIP.xDAWN_LDA.value) pipelines[PIP.ERPCov_CvxMDM_Dist.value] = QuantumMDMWithRiemannianPipeline( - convex_metric="distance", quantum=False + metric="distance", quantum=False ) placeholder(PIP.ERPCov_CvxMDM_Dist.value) # Quantum Pipelines pipelines[PIP.ERPCov_QMDM_Dist.value] = QuantumMDMWithRiemannianPipeline( - convex_metric="distance", quantum=True + metric="distance", quantum=True ) placeholder(PIP.ERPCov_QMDM_Dist.value) pipelines[PIP.ERPCov_QMDM_Dist.value] = QuantumMDMWithRiemannianPipeline( - convex_metric="distance", quantum=True + metric="distance", quantum=True ) placeholder(PIP.ERPCov_QMDM_Dist.value) diff --git a/examples/ERP/classify_P300_bi_quantum_mdm.py b/examples/ERP/classify_P300_bi_quantum_mdm.py index 0d889c1b..52e8bb52 100644 --- a/examples/ERP/classify_P300_bi_quantum_mdm.py +++ b/examples/ERP/classify_P300_bi_quantum_mdm.py @@ -7,7 +7,7 @@ optimization problems. These optimization problems are translated to Qiskit using Docplex and additional glue code. These optimizations are enabled when we use cpm mean or cpm distance. This is set -using the 'cpm_metric' parameter of the QuantumMDMWithRiemannianPipeline. +using the 'metric' parameter of the QuantumMDMWithRiemannianPipeline. Classification can be run either on emulation or real quantum computer. @@ -108,11 +108,11 @@ pipelines = {} pipelines["mean=cpm-le/distance=logeuclid"] = QuantumMDMWithRiemannianPipeline( - cpm_metric="mean", quantum=quantum + metric="mean", quantum=quantum ) pipelines["mean=logeuclid/distance=cpm-le"] = QuantumMDMWithRiemannianPipeline( - cpm_metric="distance", quantum=quantum + metric="distance", quantum=quantum ) pipelines["Voting cpm-le"] = QuantumMDMVotingClassifier(quantum=quantum) diff --git a/examples/MI/classify_alexmi_with_quantum_pipeline.py b/examples/MI/classify_alexmi_with_quantum_pipeline.py index cb68a523..ac482c49 100644 --- a/examples/MI/classify_alexmi_with_quantum_pipeline.py +++ b/examples/MI/classify_alexmi_with_quantum_pipeline.py @@ -69,7 +69,7 @@ # Will run QAOA under the hood pipelines["mean=logeuclid/distance=cpm"] = QuantumMDMWithRiemannianPipeline( - cpm_metric="distance", quantum=True + metric="distance", quantum=True ) # Classical baseline for evaluation diff --git a/pyriemann_qiskit/pipelines.py b/pyriemann_qiskit/pipelines.py index a6403013..0b453887 100644 --- a/pyriemann_qiskit/pipelines.py +++ b/pyriemann_qiskit/pipelines.py @@ -310,9 +310,9 @@ class QuantumMDMWithRiemannianPipeline(BasePipeline): Parameters ---------- - cpm_metric : string (default: "distance") + metric : string (default: "distance") `metric` passed to the inner QuanticMDM depends on the - `cpm_metric` as follows (cpm_metric => metric): + `metric` as follows (metric => metric): - "distance" => {mean=logeuclid, distance=cpm-le}, - "mean" => {mean=cpm-le, distance=logeuclid}, @@ -351,14 +351,14 @@ class QuantumMDMWithRiemannianPipeline(BasePipeline): def __init__( self, - cpm_metric="distance", + metric="distance", quantum=True, q_account_token=None, verbose=True, shots=1024, upper_bound=7, ): - self.cpm_metric = cpm_metric + self.metric = metric self.quantum = quantum self.q_account_token = q_account_token self.verbose = verbose @@ -368,9 +368,9 @@ def __init__( BasePipeline.__init__(self, "QuantumMDMWithRiemannianPipeline") def _create_pipe(self): - if self.cpm_metric == "both": + if self.metric == "both": metric = {"mean": "cpm_le", "distance": "cpm_le"} - elif self.cpm_metric == "mean": + elif self.metric == "mean": metric = {"mean": "cpm_le", "distance": "logeuclid"} else: metric = {"mean": "logeuclid", "distance": "cpm_le"} @@ -407,8 +407,8 @@ class QuantumMDMVotingClassifier(BasePipeline): Voting classifier with two configurations of QuantumMDMWithRiemannianPipeline: - - with mean = cpm and distance = euclid, - - with mean = logeuclid and distance = cpm. + - with mean = cpm-le and distance = logeuclid, + - with mean = logeuclid and distance = cpm-le. Parameters ---------- From 80c145d9a2739d8873446a2a055a2e8b74057962 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 22:01:26 +0000 Subject: [PATCH 14/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- benchmarks/light_benchmark.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/benchmarks/light_benchmark.py b/benchmarks/light_benchmark.py index 171844da..da119a04 100644 --- a/benchmarks/light_benchmark.py +++ b/benchmarks/light_benchmark.py @@ -81,9 +81,7 @@ shots=100, spsa_trials=5, two_local_reps=2, params={"seed": 42} ) -pipelines["QMDM_mean"] = QuantumMDMWithRiemannianPipeline( - metric="mean", quantum=True -) +pipelines["QMDM_mean"] = QuantumMDMWithRiemannianPipeline(metric="mean", quantum=True) pipelines["QMDM_dist"] = QuantumMDMWithRiemannianPipeline( metric="distance", quantum=True From e2d654bdcd888f4b90514e23a4e43fa83e989fab Mon Sep 17 00:00:00 2001 From: gcattan Date: Tue, 20 Feb 2024 21:32:20 +0100 Subject: [PATCH 15/90] Update pyriemann_qiskit/utils/mean.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/mean.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 37fa1e84..b76b681b 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -78,7 +78,7 @@ def _fro_dist(A, B): def mean_logeuclid_cpm( - covmats, sample_weight=None, optimizer=ClassicalOptimizer(), shrink=True + X, sample_weight=None, optimizer=ClassicalOptimizer(), shrink=True ): """Constraint Programm Model (CPM) formulation of the mean with log-euclidian distance. From 663c863039ca2a8e7f07c4f2e630badae329338b Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Tue, 20 Feb 2024 21:37:09 +0100 Subject: [PATCH 16/90] - remove shrink - expose optimizer - rename covmats -> X --- pyriemann_qiskit/utils/docplex.py | 17 +++++++++++++++-- pyriemann_qiskit/utils/mean.py | 12 +++++------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/pyriemann_qiskit/utils/docplex.py b/pyriemann_qiskit/utils/docplex.py index 32397b44..5e94b2d6 100644 --- a/pyriemann_qiskit/utils/docplex.py +++ b/pyriemann_qiskit/utils/docplex.py @@ -307,19 +307,32 @@ class ClassicalOptimizer(pyQiskitOptimizer): """Wrapper for the classical Cobyla optimizer. + Attributes + ---------- + optimizer : OptimizationAlgorithm + An instance of OptimizationAlgorithm [1]_ + Notes ----- .. versionadded:: 0.0.2 .. versionchanged:: 0.0.4 + .. versionchanged:: 0.2.0 + Add attribute `optimizer`. See Also -------- pyQiskitOptimizer + References + ---------- + .. [1] \ + https://qiskit-community.github.io/qiskit-optimization/stubs/qiskit_optimization.algorithms.OptimizationAlgorithm.html#optimizationalgorithm + """ - def __init__(self): + def __init__(self, optimizer=CobylaOptimizer(rhobeg=2.1, rhoend=0.000001)): pyQiskitOptimizer.__init__(self) + self.optimizer = optimizer """Helper to create a docplex representation of a covariance matrix variable. @@ -360,7 +373,7 @@ def covmat_var(self, prob, channels, name): return square_cont_mat_var(prob, channels, name) def _solve_qp(self, qp, reshape=True): - result = CobylaOptimizer(rhobeg=2.1, rhoend=0.000001).solve(qp).x + result = self.optimizer.solve(qp).x if reshape: n_channels = int(math.sqrt(result.shape[0])) return np.reshape(result, (n_channels, n_channels)) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index b76b681b..e846037f 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -4,6 +4,7 @@ from pyriemann_qiskit.utils.docplex import ClassicalOptimizer, get_global_optimizer from pyriemann.estimation import Shrinkage from pyriemann.utils.base import logm, expm +from qiskit_optimization.algorithms import ADMMOptimizer import numpy as np @@ -78,22 +79,19 @@ def _fro_dist(A, B): def mean_logeuclid_cpm( - X, sample_weight=None, optimizer=ClassicalOptimizer(), shrink=True + X, sample_weight=None, optimizer=ClassicalOptimizer(optimizer=ADMMOptimizer()) ): """Constraint Programm Model (CPM) formulation of the mean with log-euclidian distance. Parameters ---------- - covmats: ndarray, shape (n_matrices, n_channels, n_channels) + X: ndarray, shape (n_matrices, n_channels, n_channels) Set of SPD matrices. sample_weights: None | ndarray, shape (n_matrices,), default=None Weights for each matrix. Never used in practice. It is kept only for standardization with pyRiemann. optimizer: pyQiskitOptimizer An instance of pyQiskitOptimizer. - shrink: boolean (default: true) - If True, it applies shrinkage regularization [2]_ - of the resulting covariance matrix. Returns ------- @@ -106,8 +104,8 @@ def mean_logeuclid_cpm( """ - log_covmats = logm(covmats) - result = mean_euclid_cpm(log_covmats, sample_weight, optimizer, shrink) + log_X = logm(X) + result = mean_euclid_cpm(log_X, sample_weight, optimizer, shrink=False) return expm(result) From 297117a5ca96b8b863944d90f1e5091c8adab93b Mon Sep 17 00:00:00 2001 From: gcattan Date: Tue, 20 Feb 2024 21:40:24 +0100 Subject: [PATCH 17/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 30a6cebd..f2226218 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -6,8 +6,10 @@ from pyriemann.utils.base import logm -def logeucl_dist_cpm(X, y, optimizer=ClassicalOptimizer()): - """Constraint Programming Model (CPM) formulation of the MDM algorithm with log-Euclidean metric. +def logeucl_dist_cpm(A, B, optimizer=ClassicalOptimizer()): + """Log-Euclidean distance by Constraint Programming Model. + + Constraint Programming Model (CPM) formulation of the Log-Euclidean distance. Parameters ---------- From d15a905e27fd2bb2501b7168768cee24ba5a4a0a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 20:41:09 +0000 Subject: [PATCH 18/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- pyriemann_qiskit/utils/distance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index f2226218..5dfc2a37 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -8,7 +8,7 @@ def logeucl_dist_cpm(A, B, optimizer=ClassicalOptimizer()): """Log-Euclidean distance by Constraint Programming Model. - + Constraint Programming Model (CPM) formulation of the Log-Euclidean distance. Parameters From f9fedfa4cdc6dc933eca666df416bdb0034cd492 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Tue, 20 Feb 2024 21:45:07 +0100 Subject: [PATCH 19/90] add missing reference to distance --- pyriemann_qiskit/utils/distance.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 5dfc2a37..3e6c82b5 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -6,8 +6,8 @@ from pyriemann.utils.base import logm -def logeucl_dist_cpm(A, B, optimizer=ClassicalOptimizer()): - """Log-Euclidean distance by Constraint Programming Model. +def logeucl_dist_cpm(X, y, optimizer=ClassicalOptimizer()): + """Log-Euclidean distance [1]_ by Constraint Programming Model [2]_. Constraint Programming Model (CPM) formulation of the Log-Euclidean distance. @@ -34,7 +34,11 @@ def logeucl_dist_cpm(A, B, optimizer=ClassicalOptimizer()): References ---------- .. [1] \ + K. Zhao, A. Wiliem, S. Chen, and B. C. Lovell, + ‘Convex Class Model on Symmetric Positive Definite Manifolds’, arXiv:1806.05343 [cs], May 2019. + .. [2] \ http://ibmdecisionoptimization.github.io/docplex-doc/mp/_modules/docplex/mp/model.html#Model + """ optimizer = get_global_optimizer(optimizer) From 865a0e51421dd4d4725af27987ee52e05d03bae4 Mon Sep 17 00:00:00 2001 From: gcattan Date: Tue, 20 Feb 2024 21:47:51 +0100 Subject: [PATCH 20/90] Update pyriemann_qiskit/utils/mean.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/mean.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index e846037f..b6d308b9 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -81,7 +81,10 @@ def _fro_dist(A, B): def mean_logeuclid_cpm( X, sample_weight=None, optimizer=ClassicalOptimizer(optimizer=ADMMOptimizer()) ): - """Constraint Programm Model (CPM) formulation of the mean with log-euclidian distance. + """Log-Euclidean mean with Constraint Programm Model. + + Constraint Programm Model (CPM) formulation of the mean + with Log-Euclidean distance. Parameters ---------- From 06307782d1dd07ddae71fa096c744b8d8891b007 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 20:48:20 +0000 Subject: [PATCH 21/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- pyriemann_qiskit/utils/mean.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index b6d308b9..3dac0ed7 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -82,8 +82,8 @@ def mean_logeuclid_cpm( X, sample_weight=None, optimizer=ClassicalOptimizer(optimizer=ADMMOptimizer()) ): """Log-Euclidean mean with Constraint Programm Model. - - Constraint Programm Model (CPM) formulation of the mean + + Constraint Programm Model (CPM) formulation of the mean with Log-Euclidean distance. Parameters From b843c4127ba86933cfef592d0cc7e4f1435f3919 Mon Sep 17 00:00:00 2001 From: gcattan Date: Tue, 20 Feb 2024 21:52:11 +0100 Subject: [PATCH 22/90] Update pyriemann_qiskit/utils/mean.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/mean.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 3dac0ed7..9188c18f 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -19,7 +19,10 @@ def fro_mean_convex(): def mean_euclid_cpm( covmats, sample_weight=None, optimizer=ClassicalOptimizer(), shrink=True ): - """Constraint Programm Model (CPM) formulation of the mean with Frobenius distance. + """Euclidean mean with Constraint Programm Model. + + Constraint Programm Model (CPM) formulation of the mean + with Euclidean distance. Parameters ---------- From 5d9bc28bb96523938887fc2091e02762de6b4f40 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 20:53:11 +0000 Subject: [PATCH 23/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- pyriemann_qiskit/utils/mean.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 9188c18f..a39ca1e5 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -20,7 +20,7 @@ def mean_euclid_cpm( covmats, sample_weight=None, optimizer=ClassicalOptimizer(), shrink=True ): """Euclidean mean with Constraint Programm Model. - + Constraint Programm Model (CPM) formulation of the mean with Euclidean distance. From 803d76ccd41c6d203dc129b22ab4ddc345db16da Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Tue, 20 Feb 2024 21:53:12 +0100 Subject: [PATCH 24/90] add references to cpm_le --- pyriemann_qiskit/utils/mean.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index a39ca1e5..2b2d22c8 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -84,7 +84,7 @@ def _fro_dist(A, B): def mean_logeuclid_cpm( X, sample_weight=None, optimizer=ClassicalOptimizer(optimizer=ADMMOptimizer()) ): - """Log-Euclidean mean with Constraint Programm Model. + """Log-Euclidean mean [1]_ with Constraint Programming Model [2]_. Constraint Programm Model (CPM) formulation of the mean with Log-Euclidean distance. @@ -108,6 +108,15 @@ def mean_logeuclid_cpm( ----- .. versionadded:: 0.2.0 + References + ---------- + .. [1] \ + Geometric means in a novel vector space structure on symmetric positive-definite matrices\ + V. Arsigny, P. Fillard, X. Pennec, and N. Ayache.\ + SIAM Journal on Matrix Analysis and Applications. Volume 29, Issue 1 (2007). + .. [2] \ + http://ibmdecisionoptimization.github.io/docplex-doc/mp/_modules/docplex/mp/model.html#Model + """ log_X = logm(X) From aba96385ff3a8b10447036d90f6a34493d99d7fb Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Tue, 20 Feb 2024 21:56:46 +0100 Subject: [PATCH 25/90] fix references --- pyriemann_qiskit/utils/distance.py | 2 +- pyriemann_qiskit/utils/mean.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 3e6c82b5..f21e2fb2 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -37,7 +37,7 @@ def logeucl_dist_cpm(X, y, optimizer=ClassicalOptimizer()): K. Zhao, A. Wiliem, S. Chen, and B. C. Lovell, ‘Convex Class Model on Symmetric Positive Definite Manifolds’, arXiv:1806.05343 [cs], May 2019. .. [2] \ - http://ibmdecisionoptimization.github.io/docplex-doc/mp/_modules/docplex/mp/model.html#Model + http://ibmdecisionoptimization.github.io/docplex-doc/cp/creating_model.html """ diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 2b2d22c8..908c62ae 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -19,9 +19,9 @@ def fro_mean_convex(): def mean_euclid_cpm( covmats, sample_weight=None, optimizer=ClassicalOptimizer(), shrink=True ): - """Euclidean mean with Constraint Programm Model. + """Euclidean mean with Constraint Programming Model [1]_. - Constraint Programm Model (CPM) formulation of the mean + Constraint Programming Model (CPM) formulation of the mean with Euclidean distance. Parameters @@ -51,7 +51,7 @@ def mean_euclid_cpm( References ---------- .. [1] \ - http://ibmdecisionoptimization.github.io/docplex-doc/mp/_modules/docplex/mp/model.html#Model + http://ibmdecisionoptimization.github.io/docplex-doc/cp/creating_model.html .. [2] \ https://pyriemann.readthedocs.io/en/v0.4/generated/pyriemann.estimation.Shrinkage.html """ @@ -86,7 +86,7 @@ def mean_logeuclid_cpm( ): """Log-Euclidean mean [1]_ with Constraint Programming Model [2]_. - Constraint Programm Model (CPM) formulation of the mean + Constraint Programming Model (CPM) formulation of the mean with Log-Euclidean distance. Parameters @@ -115,7 +115,7 @@ def mean_logeuclid_cpm( V. Arsigny, P. Fillard, X. Pennec, and N. Ayache.\ SIAM Journal on Matrix Analysis and Applications. Volume 29, Issue 1 (2007). .. [2] \ - http://ibmdecisionoptimization.github.io/docplex-doc/mp/_modules/docplex/mp/model.html#Model + http://ibmdecisionoptimization.github.io/docplex-doc/cp/creating_model.html """ From 1c88275a5cea74e0a086da5708fb59a6e2a1fc45 Mon Sep 17 00:00:00 2001 From: gcattan Date: Wed, 21 Feb 2024 11:26:44 +0100 Subject: [PATCH 26/90] Update pyriemann_qiskit/utils/mean.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/mean.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 908c62ae..19ceac78 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -19,9 +19,9 @@ def fro_mean_convex(): def mean_euclid_cpm( covmats, sample_weight=None, optimizer=ClassicalOptimizer(), shrink=True ): - """Euclidean mean with Constraint Programming Model [1]_. + """Euclidean mean with Constraint Programming Model. - Constraint Programming Model (CPM) formulation of the mean + Constraint Programming Model (CPM) [1]_ formulation of the mean with Euclidean distance. Parameters From 5c019dcd7ef087e4ac0b6604bfef0ea8252998a8 Mon Sep 17 00:00:00 2001 From: gcattan Date: Wed, 21 Feb 2024 11:26:54 +0100 Subject: [PATCH 27/90] Update pyriemann_qiskit/utils/mean.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/mean.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 19ceac78..28e1d2b7 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -84,10 +84,10 @@ def _fro_dist(A, B): def mean_logeuclid_cpm( X, sample_weight=None, optimizer=ClassicalOptimizer(optimizer=ADMMOptimizer()) ): - """Log-Euclidean mean [1]_ with Constraint Programming Model [2]_. + """Log-Euclidean mean with Constraint Programming Model. - Constraint Programming Model (CPM) formulation of the mean - with Log-Euclidean distance. + Constraint Programming Model (CPM) [2]_ formulation of the mean + with Log-Euclidean distance [1]_. Parameters ---------- From d00ad947d1da2684628f815a6f62d852797c8ef4 Mon Sep 17 00:00:00 2001 From: gcattan Date: Wed, 21 Feb 2024 11:27:27 +0100 Subject: [PATCH 28/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index f21e2fb2..dae044c6 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -7,9 +7,9 @@ def logeucl_dist_cpm(X, y, optimizer=ClassicalOptimizer()): - """Log-Euclidean distance [1]_ by Constraint Programming Model [2]_. + """Log-Euclidean distance by Constraint Programming Model. - Constraint Programming Model (CPM) formulation of the Log-Euclidean distance. + Constraint Programming Model (CPM) [2]_ formulation of the Log-Euclidean distance [1]_. Parameters ---------- From fabc4263238cbc9157ed051cfcbd9e8e072cf595 Mon Sep 17 00:00:00 2001 From: gcattan Date: Wed, 21 Feb 2024 11:27:39 +0100 Subject: [PATCH 29/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index dae044c6..0d4e7dca 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -35,7 +35,7 @@ def logeucl_dist_cpm(X, y, optimizer=ClassicalOptimizer()): ---------- .. [1] \ K. Zhao, A. Wiliem, S. Chen, and B. C. Lovell, - ‘Convex Class Model on Symmetric Positive Definite Manifolds’, arXiv:1806.05343 [cs], May 2019. + ‘Convex Class Model on Symmetric Positive Definite Manifolds’, Image and Vision Computing, 2019. .. [2] \ http://ibmdecisionoptimization.github.io/docplex-doc/cp/creating_model.html From 5eab6500c874672f3efc2e05fddb142ea175b545 Mon Sep 17 00:00:00 2001 From: gcattan Date: Wed, 21 Feb 2024 11:44:04 +0100 Subject: [PATCH 30/90] Update pyriemann_qiskit/utils/mean.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/mean.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 28e1d2b7..e7418ecc 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -124,5 +124,5 @@ def mean_logeuclid_cpm( return expm(result) -mean_functions["cpm_fro"] = mean_euclid_cpm -mean_functions["cpm_le"] = mean_logeuclid_cpm +mean_functions["euclid_cpm"] = mean_euclid_cpm +mean_functions["logeuclid_cpm"] = mean_logeuclid_cpm From 7080dcec9609ed8405d9bed85c67dd328c884e90 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Wed, 21 Feb 2024 11:48:49 +0100 Subject: [PATCH 31/90] - use logeuclid_cpm everywhere - rename logeuclid_dist_cpm by distance_logeuclid_cpm --- doc/api.rst | 2 +- examples/ERP/classify_P300_bi_quantum_mdm.py | 6 +++--- pyriemann_qiskit/classification.py | 2 +- pyriemann_qiskit/pipelines.py | 18 +++++++++--------- pyriemann_qiskit/utils/__init__.py | 4 ++-- pyriemann_qiskit/utils/distance.py | 10 +++++----- tests/test_utils_distance.py | 8 ++++---- tests/test_utils_mean.py | 2 +- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/doc/api.rst b/doc/api.rst index fa3fc887..06e1d570 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -107,7 +107,7 @@ Distance .. autosummary:: :toctree: generated/ - logeucl_dist_cpm + distance_logeuclid_cpm Docplex ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/examples/ERP/classify_P300_bi_quantum_mdm.py b/examples/ERP/classify_P300_bi_quantum_mdm.py index 52e8bb52..8ecba8cc 100644 --- a/examples/ERP/classify_P300_bi_quantum_mdm.py +++ b/examples/ERP/classify_P300_bi_quantum_mdm.py @@ -107,15 +107,15 @@ pipelines = {} -pipelines["mean=cpm-le/distance=logeuclid"] = QuantumMDMWithRiemannianPipeline( +pipelines["mean=logeuclid_cpm/distance=logeuclid"] = QuantumMDMWithRiemannianPipeline( metric="mean", quantum=quantum ) -pipelines["mean=logeuclid/distance=cpm-le"] = QuantumMDMWithRiemannianPipeline( +pipelines["mean=logeuclid/distance=logeuclid_cpm"] = QuantumMDMWithRiemannianPipeline( metric="distance", quantum=quantum ) -pipelines["Voting cpm-le"] = QuantumMDMVotingClassifier(quantum=quantum) +pipelines["Voting logeuclid_cpm"] = QuantumMDMVotingClassifier(quantum=quantum) ############################################################################## # Run evaluation diff --git a/pyriemann_qiskit/classification.py b/pyriemann_qiskit/classification.py index d1a917c4..63f894b3 100644 --- a/pyriemann_qiskit/classification.py +++ b/pyriemann_qiskit/classification.py @@ -646,7 +646,7 @@ class QuanticMDM(QuanticClassifierBase): def __init__( self, - metric={"mean": "logeuclid", "distance": "cpm_le"}, + metric={"mean": "logeuclid", "distance": "logeuclid_cpm"}, quantum=True, q_account_token=None, verbose=True, diff --git a/pyriemann_qiskit/pipelines.py b/pyriemann_qiskit/pipelines.py index 0b453887..eb391054 100644 --- a/pyriemann_qiskit/pipelines.py +++ b/pyriemann_qiskit/pipelines.py @@ -314,9 +314,9 @@ class QuantumMDMWithRiemannianPipeline(BasePipeline): `metric` passed to the inner QuanticMDM depends on the `metric` as follows (metric => metric): - - "distance" => {mean=logeuclid, distance=cpm-le}, - - "mean" => {mean=cpm-le, distance=logeuclid}, - - "both" => {mean=cpm-le, distance=cpm-le}, + - "distance" => {mean=logeuclid, distance=logeuclid_cpm}, + - "mean" => {mean=logeuclid_cpm, distance=logeuclid}, + - "both" => {mean=logeuclid_cpm, distance=logeuclid_cpm}, - other => same as "distance". quantum : bool (default: True) - If true will run on local or remote backend @@ -369,13 +369,13 @@ def __init__( def _create_pipe(self): if self.metric == "both": - metric = {"mean": "cpm_le", "distance": "cpm_le"} + metric = {"mean": "logeuclid_cpm", "distance": "logeuclid_cpm"} elif self.metric == "mean": - metric = {"mean": "cpm_le", "distance": "logeuclid"} + metric = {"mean": "logeuclid_cpm", "distance": "logeuclid"} else: - metric = {"mean": "logeuclid", "distance": "cpm_le"} + metric = {"mean": "logeuclid", "distance": "logeuclid_cpm"} - if metric["mean"] == "cpm_le": + if metric["mean"] == "logeuclid_cpm": if self.quantum: covariances = XdawnCovariances( nfilter=1, estimator="scm", xdawn_estimator="lwf" @@ -407,8 +407,8 @@ class QuantumMDMVotingClassifier(BasePipeline): Voting classifier with two configurations of QuantumMDMWithRiemannianPipeline: - - with mean = cpm-le and distance = logeuclid, - - with mean = logeuclid and distance = cpm-le. + - with mean = logeuclid_cpm and distance = logeuclid, + - with mean = logeuclid and distance = logeuclid_cpm. Parameters ---------- diff --git a/pyriemann_qiskit/utils/__init__.py b/pyriemann_qiskit/utils/__init__.py index f55e99c2..fa63aabc 100644 --- a/pyriemann_qiskit/utils/__init__.py +++ b/pyriemann_qiskit/utils/__init__.py @@ -18,7 +18,7 @@ add_moabb_dataframe_results_to_caches, convert_caches_to_dataframes, ) -from .distance import logeucl_dist_cpm +from .distance import distance_logeuclid_cpm __all__ = [ "hyper_params_factory", @@ -36,7 +36,7 @@ "NaiveQAOAOptimizer", "set_global_optimizer", "get_global_optimizer", - "logeucl_dist_cpm", + "distance_logeuclid_cpm", "FirebaseConnector", "Cache", "generate_caches", diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 0d4e7dca..85d8e3fe 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -6,7 +6,7 @@ from pyriemann.utils.base import logm -def logeucl_dist_cpm(X, y, optimizer=ClassicalOptimizer()): +def distance_logeuclid_cpm(X, y, optimizer=ClassicalOptimizer()): """Log-Euclidean distance by Constraint Programming Model. Constraint Programming Model (CPM) [2]_ formulation of the Log-Euclidean distance [1]_. @@ -73,9 +73,9 @@ def log_prod(m1, m2): def predict_distances(mdm, X): - if mdm.metric_dist == "cpm_le": + if mdm.metric_dist == "logeuclid_cpm": centroids = np.array(mdm.covmeans_) - return np.array([logeucl_dist_cpm(centroids, x) for x in X]) + return np.array([distance_logeuclid_cpm(centroids, x) for x in X]) else: return _mdm_predict_distances_original(mdm, X) @@ -84,7 +84,7 @@ def predict_distances(mdm, X): # This is only for validation inside the MDM. # In fact, we override the _predict_distances method -# inside MDM to directly use logeucl_dist_cpm when the metric is "cpm_le" +# inside MDM to directly use distance_logeuclid_cpm when the metric is "logeuclid_cpm" # This is due to the fact the the signature of this method is different from # the usual distance functions. -distance_functions["cpm_le"] = logeucl_dist_cpm +distance_functions["logeuclid_cpm"] = distance_logeuclid_cpm diff --git a/tests/test_utils_distance.py b/tests/test_utils_distance.py index d711a497..1c23a03a 100644 --- a/tests/test_utils_distance.py +++ b/tests/test_utils_distance.py @@ -3,7 +3,7 @@ from pyriemann_qiskit.utils import ( ClassicalOptimizer, NaiveQAOAOptimizer, - logeucl_dist_cpm, + distance_logeuclid_cpm, ) from pyriemann_qiskit.datasets import get_mne_sample from pyriemann.classification import MDM @@ -13,7 +13,7 @@ def test_performance(): - metric = {"mean": "logeuclid", "distance": "cpm_le"} + metric = {"mean": "logeuclid", "distance": "logeuclid_cpm"} clf = make_pipeline(XdawnCovariances(), MDM(metric=metric)) skf = StratifiedKFold(n_splits=3) @@ -23,10 +23,10 @@ def test_performance(): @pytest.mark.parametrize("optimizer", [ClassicalOptimizer(), NaiveQAOAOptimizer()]) -def test_logeucl_dist_cpm(optimizer): +def test_distance_logeuclid_cpm(optimizer): X_0 = np.array([[0.9, 1.1], [0.9, 1.1]]) X_1 = X_0 + 1 X = np.stack((X_0, X_1)) y = (X_0 + X_1) / 3 - distances = logeucl_dist_cpm(X, y, optimizer=optimizer) + distances = distance_logeuclid_cpm(X, y, optimizer=optimizer) assert distances.argmin() == 0 diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index 3545062e..d03a4b79 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -10,7 +10,7 @@ def test_performance(get_covmats, get_labels): - metric = {"mean": "cpm_fro", "distance": "euclid"} + metric = {"mean": "euclid_cpm", "distance": "euclid"} clf = make_pipeline(XdawnCovariances(), MDM(metric=metric)) skf = StratifiedKFold(n_splits=5) From c05165972e3f83af095c9bce1ab6b2efe6c6db51 Mon Sep 17 00:00:00 2001 From: gcattan Date: Wed, 21 Feb 2024 13:48:48 +0100 Subject: [PATCH 32/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 85d8e3fe..7f58f24c 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -5,7 +5,12 @@ from pyriemann.utils.distance import distance_functions from pyriemann.utils.base import logm - +@deprecated( + "logeucl_dist_convex is deprecated and will be removed in 0.3.0; " + "please use distance_logeuclid_cpm." +) +def logeucl_dist_convex(): + pass def distance_logeuclid_cpm(X, y, optimizer=ClassicalOptimizer()): """Log-Euclidean distance by Constraint Programming Model. From 8e7c898dc70d24e47c372ef98fc9c78b98f59a05 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 12:51:29 +0000 Subject: [PATCH 33/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- pyriemann_qiskit/utils/distance.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 7f58f24c..34fc4e5d 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -5,12 +5,15 @@ from pyriemann.utils.distance import distance_functions from pyriemann.utils.base import logm + @deprecated( "logeucl_dist_convex is deprecated and will be removed in 0.3.0; " "please use distance_logeuclid_cpm." ) def logeucl_dist_convex(): pass + + def distance_logeuclid_cpm(X, y, optimizer=ClassicalOptimizer()): """Log-Euclidean distance by Constraint Programming Model. From c43d50a1494ff967211b66055c331b31ba8d5c1a Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Wed, 21 Feb 2024 12:10:11 +0100 Subject: [PATCH 34/90] add test --- tests/test_utils_mean.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index d03a4b79..4130d45f 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -35,11 +35,14 @@ def test_analytic_vs_cpm_mean(get_covmats, means): assert np.allclose(C, C_analytic, atol=0.001) -def test_mean_cpm_shape(get_covmats): +@pytest.mark.parametrize( + "mean", [mean_euclid_cpm, mean_logeuclid_cpm] +) +def test_mean_cpm_shape(get_covmats, mean): """Test the shape of mean""" n_trials, n_channels = 5, 3 covmats = get_covmats(n_trials, n_channels) - C = mean_euclid_cpm(covmats) + C = mean(covmats) assert C.shape == (n_channels, n_channels) From 3549dc0223211c39dd0c9b56f71d5371bb851256 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Wed, 21 Feb 2024 14:48:17 +0100 Subject: [PATCH 35/90] - remove shrinkage - add regularization parameter to quanticMDM --- pyriemann_qiskit/classification.py | 13 ++++++++++++- pyriemann_qiskit/utils/mean.py | 15 +++++---------- tests/test_utils_mean.py | 10 +++++----- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/pyriemann_qiskit/classification.py b/pyriemann_qiskit/classification.py index 63f894b3..2b6c3fbd 100644 --- a/pyriemann_qiskit/classification.py +++ b/pyriemann_qiskit/classification.py @@ -529,6 +529,7 @@ def __init__( ) self.optimizer = optimizer self.gen_var_form = gen_var_form + self.regularization = regularization def _init_algo(self, n_features): self._log("VQC training...") @@ -593,7 +594,8 @@ class QuanticMDM(QuanticClassifierBase): .. versionchanged:: 0.1.0 Fix: copy estimator not keeping base class parameters. .. versionchanged:: 0.2.0 - Add seed parameter + Add seed parameter. + Add regularization parameter. Parameters ---------- @@ -624,6 +626,8 @@ class QuanticMDM(QuanticClassifierBase): Random seed for the simulation upper_bound : int (default: 7) The maximum integer value for matrix normalization. + regularization: MixinTransformer (defulat: None) + Additional post-processing to regularize means. See Also -------- @@ -653,6 +657,7 @@ def __init__( shots=1024, seed=None, upper_bound=7, + regularization=None ): QuanticClassifierBase.__init__( self, quantum, q_account_token, verbose, shots, None, seed @@ -674,6 +679,12 @@ def _init_algo(self, n_features): set_global_optimizer(self._optimizer) return classifier + def _train(self, X, y): + QuanticClassifierBase._train(self, X, y) + if self.regularization is not None: + self._classifier.covmeans_ = \ + self.regularization.fit_transform(self._classifier.covmeans_) + def predict(self, X): """Calculates the predictions. diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index e7418ecc..08fd5486 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -2,7 +2,6 @@ from docplex.mp.model import Model from pyriemann.utils.mean import mean_functions from pyriemann_qiskit.utils.docplex import ClassicalOptimizer, get_global_optimizer -from pyriemann.estimation import Shrinkage from pyriemann.utils.base import logm, expm from qiskit_optimization.algorithms import ADMMOptimizer import numpy as np @@ -17,7 +16,7 @@ def fro_mean_convex(): def mean_euclid_cpm( - covmats, sample_weight=None, optimizer=ClassicalOptimizer(), shrink=True + covmats, sample_weight=None, optimizer=ClassicalOptimizer() ): """Euclidean mean with Constraint Programming Model. @@ -33,9 +32,6 @@ def mean_euclid_cpm( It is kept only for standardization with pyRiemann. optimizer: pyQiskitOptimizer An instance of pyQiskitOptimizer. - shrink: boolean (default: true) - If True, it applies shrinkage regularization [2]_ - of the resulting covariance matrix. Returns ------- @@ -47,13 +43,14 @@ def mean_euclid_cpm( .. versionadded:: 0.0.3 .. versionchanged:: 0.0.4 Add regularization of the results. + .. versionchanged:: 0.2.0 + Rename from `fro_mean_convex` to `mean_euclid_cpm` + Remove shrinkage References ---------- .. [1] \ http://ibmdecisionoptimization.github.io/docplex-doc/cp/creating_model.html - .. [2] \ - https://pyriemann.readthedocs.io/en/v0.4/generated/pyriemann.estimation.Shrinkage.html """ optimizer = get_global_optimizer(optimizer) @@ -76,8 +73,6 @@ def _fro_dist(A, B): result = optimizer.solve(prob) - if shrink: - return Shrinkage(shrinkage=0.9).transform([result])[0] return result @@ -120,7 +115,7 @@ def mean_logeuclid_cpm( """ log_X = logm(X) - result = mean_euclid_cpm(log_X, sample_weight, optimizer, shrink=False) + result = mean_euclid_cpm(log_X, sample_weight, optimizer) return expm(result) diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index 4130d45f..48b08e9c 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -30,7 +30,7 @@ def test_analytic_vs_cpm_mean(get_covmats, means): analytic_mean, cpm_mean = means n_trials, n_channels = 5, 3 covmats = get_covmats(n_trials, n_channels) - C = cpm_mean(covmats, shrink=False) + C = cpm_mean(covmats) C_analytic = analytic_mean(covmats) assert np.allclose(C, C_analytic, atol=0.001) @@ -52,7 +52,7 @@ def test_mean_cpm_all_zeros(optimizer): is a matrix filled with zeros""" n_trials, n_channels = 5, 2 covmats = np.zeros((n_trials, n_channels, n_channels)) - C = mean_euclid_cpm(covmats, optimizer=optimizer, shrink=False) + C = mean_euclid_cpm(covmats, optimizer=optimizer) assert np.allclose(covmats[0], C, atol=0.001) @@ -61,7 +61,7 @@ def test_mean_cpm_all_ones(): is a matrix filled with ones""" n_trials, n_channels = 5, 2 covmats = np.ones((n_trials, n_channels, n_channels)) - C = mean_euclid_cpm(covmats, shrink=False) + C = mean_euclid_cpm(covmats) assert np.allclose(covmats[0], C, atol=0.001) @@ -70,7 +70,7 @@ def test_mean_cpm_all_equals(): is a matrix identical to the input""" n_trials, n_channels, value = 5, 2, 2.5 covmats = np.full((n_trials, n_channels, n_channels), value) - C = mean_euclid_cpm(covmats, shrink=False) + C = mean_euclid_cpm(covmats) assert np.allclose(covmats[0], C, atol=0.001) @@ -81,5 +81,5 @@ def test_mean_cpm_mixed(): covmats_0 = np.zeros((n_trials, n_channels, n_channels)) covmats_1 = np.ones((n_trials, n_channels, n_channels)) expected_mean = np.full((n_channels, n_channels), 0.5) - C = mean_euclid_cpm(np.concatenate((covmats_0, covmats_1), axis=0), shrink=False) + C = mean_euclid_cpm(np.concatenate((covmats_0, covmats_1), axis=0)) assert np.allclose(expected_mean, C, atol=0.001) From 94c6435979fd9fb80eeb62537e662c0cca0a1ff5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:48:35 +0000 Subject: [PATCH 36/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- pyriemann_qiskit/classification.py | 7 ++++--- pyriemann_qiskit/utils/mean.py | 4 +--- tests/test_utils_mean.py | 4 +--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/pyriemann_qiskit/classification.py b/pyriemann_qiskit/classification.py index 2b6c3fbd..4f0dbd5e 100644 --- a/pyriemann_qiskit/classification.py +++ b/pyriemann_qiskit/classification.py @@ -657,7 +657,7 @@ def __init__( shots=1024, seed=None, upper_bound=7, - regularization=None + regularization=None, ): QuanticClassifierBase.__init__( self, quantum, q_account_token, verbose, shots, None, seed @@ -682,8 +682,9 @@ def _init_algo(self, n_features): def _train(self, X, y): QuanticClassifierBase._train(self, X, y) if self.regularization is not None: - self._classifier.covmeans_ = \ - self.regularization.fit_transform(self._classifier.covmeans_) + self._classifier.covmeans_ = self.regularization.fit_transform( + self._classifier.covmeans_ + ) def predict(self, X): """Calculates the predictions. diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 08fd5486..87f983ba 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -15,9 +15,7 @@ def fro_mean_convex(): pass -def mean_euclid_cpm( - covmats, sample_weight=None, optimizer=ClassicalOptimizer() -): +def mean_euclid_cpm(covmats, sample_weight=None, optimizer=ClassicalOptimizer()): """Euclidean mean with Constraint Programming Model. Constraint Programming Model (CPM) [1]_ formulation of the mean diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index 48b08e9c..cc66e2b9 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -35,9 +35,7 @@ def test_analytic_vs_cpm_mean(get_covmats, means): assert np.allclose(C, C_analytic, atol=0.001) -@pytest.mark.parametrize( - "mean", [mean_euclid_cpm, mean_logeuclid_cpm] -) +@pytest.mark.parametrize("mean", [mean_euclid_cpm, mean_logeuclid_cpm]) def test_mean_cpm_shape(get_covmats, mean): """Test the shape of mean""" n_trials, n_channels = 5, 3 From 4eec38461492d540a1e3a31f32d02c4fecc9da1b Mon Sep 17 00:00:00 2001 From: gcattan Date: Wed, 21 Feb 2024 14:51:14 +0100 Subject: [PATCH 37/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 34fc4e5d..766bcb85 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -4,6 +4,7 @@ from pyriemann.classification import MDM from pyriemann.utils.distance import distance_functions from pyriemann.utils.base import logm +from typing_extensions import deprecated @deprecated( From c7ff8abf37a7db3452b83123676764cf398d68fb Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Wed, 21 Feb 2024 14:54:20 +0100 Subject: [PATCH 38/90] rename X, y -> A, B --- pyriemann_qiskit/utils/distance.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 766bcb85..c7d08098 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -15,16 +15,16 @@ def logeucl_dist_convex(): pass -def distance_logeuclid_cpm(X, y, optimizer=ClassicalOptimizer()): +def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer()): """Log-Euclidean distance by Constraint Programming Model. Constraint Programming Model (CPM) [2]_ formulation of the Log-Euclidean distance [1]_. Parameters ---------- - X : ndarray, shape (n_classes, n_channels, n_channels) + A : ndarray, shape (n_classes, n_channels, n_channels) Set of SPD matrices. - y : ndarray, shape (n_channels, n_channels) + B : ndarray, shape (n_channels, n_channels) A trial optimizer: pyQiskitOptimizer An instance of pyQiskitOptimizer. @@ -52,7 +52,7 @@ def distance_logeuclid_cpm(X, y, optimizer=ClassicalOptimizer()): optimizer = get_global_optimizer(optimizer) - n_classes, _, _ = X.shape + n_classes, _, _ = A.shape classes = range(n_classes) def log_prod(m1, m2): @@ -63,10 +63,10 @@ def log_prod(m1, m2): # should be part of the optimizer w = optimizer.get_weights(prob, classes) - _2VecLogYD = 2 * prob.sum(w[i] * log_prod(y, X[i]) for i in classes) + _2VecLogYD = 2 * prob.sum(w[i] * log_prod(B, A[i]) for i in classes) wtDw = prob.sum( - w[i] * w[j] * log_prod(X[i], X[j]) for i in classes for j in classes + w[i] * w[j] * log_prod(A[i], A[j]) for i in classes for j in classes ) objectives = wtDw - _2VecLogYD From 1f7d4906093d5e785f5a34f0433c17946e622ed5 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Wed, 21 Feb 2024 14:58:58 +0100 Subject: [PATCH 39/90] fix failure on test due to regularization --- pyriemann_qiskit/classification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyriemann_qiskit/classification.py b/pyriemann_qiskit/classification.py index 4f0dbd5e..2eea526a 100644 --- a/pyriemann_qiskit/classification.py +++ b/pyriemann_qiskit/classification.py @@ -529,7 +529,6 @@ def __init__( ) self.optimizer = optimizer self.gen_var_form = gen_var_form - self.regularization = regularization def _init_algo(self, n_features): self._log("VQC training...") @@ -664,6 +663,7 @@ def __init__( ) self.metric = metric self.upper_bound = upper_bound + self.regularization = regularization def _init_algo(self, n_features): self._log("Quantic MDM initiating algorithm") From fc5dd3c53aa7469e80cbfc826f7ee5386d857942 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Wed, 21 Feb 2024 15:54:20 +0100 Subject: [PATCH 40/90] Tentative to improve tests on Ci update dockerfile (protobuf version) --- Dockerfile | 4 ++-- tests/test_utils_mean.py | 49 +++++++++++++++++++++++++++++----------- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/Dockerfile b/Dockerfile index a2501d96..021ecc08 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,10 +24,10 @@ RUN mkdir /root/mne_data RUN mkdir /home/mne_data ## Workaround for firestore -RUN pip install protobuf==4.25.2 +RUN pip install protobuf==4.25.3 RUN pip install google_cloud_firestore==2.14.0 ### Missing __init__ file in protobuf -RUN touch /usr/local/lib/python3.9/site-packages/protobuf-4.25.2-py3.9.egg/google/__init__.py +RUN touch /usr/local/lib/python3.9/site-packages/protobuf-4.25.3-py3.9.egg/google/__init__.py ## google.cloud.location is never used in these files, and is missing in path. RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.9/site-packages/google_cloud_firestore-2.14.0-py3.9.egg/google/cloud/firestore_v1/services/firestore/client.py' RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.9/site-packages/google_cloud_firestore-2.14.0-py3.9.egg/google/cloud/firestore_v1/services/firestore/transports/base.py' diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index cc66e2b9..ee333201 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -44,40 +44,63 @@ def test_mean_cpm_shape(get_covmats, mean): assert C.shape == (n_channels, n_channels) -@pytest.mark.parametrize("optimizer", [ClassicalOptimizer(), NaiveQAOAOptimizer()]) -def test_mean_cpm_all_zeros(optimizer): +@pytest.mark.parametrize( + "optimizer", [ClassicalOptimizer(), NaiveQAOAOptimizer()], +) +@pytest.mark.parametrize( + "mean", [mean_euclid_cpm, mean_logeuclid_cpm] +) +def test_mean_cpm_all_zeros(optimizer, mean): """Test that the mean of covariance matrices containing zeros is a matrix filled with zeros""" n_trials, n_channels = 5, 2 covmats = np.zeros((n_trials, n_channels, n_channels)) - C = mean_euclid_cpm(covmats, optimizer=optimizer) - assert np.allclose(covmats[0], C, atol=0.001) + C = mean(covmats, optimizer=optimizer) + assert np.allclose(covmats[0], C, atol=0.00001) -def test_mean_cpm_all_ones(): +@pytest.mark.parametrize( + "optimizer", [ClassicalOptimizer(), NaiveQAOAOptimizer()], +) +@pytest.mark.parametrize( + "mean", [mean_euclid_cpm, mean_logeuclid_cpm] +) +def test_mean_cpm_all_ones(optimizer, mean): """Test that the mean of covariance matrices containing ones is a matrix filled with ones""" n_trials, n_channels = 5, 2 covmats = np.ones((n_trials, n_channels, n_channels)) - C = mean_euclid_cpm(covmats) - assert np.allclose(covmats[0], C, atol=0.001) + C = mean(covmats, optimizer=optimizer) + assert np.allclose(covmats[0], C, atol=0.00001) -def test_mean_cpm_all_equals(): +@pytest.mark.parametrize( + "optimizer", [ClassicalOptimizer(), NaiveQAOAOptimizer()], +) +@pytest.mark.parametrize( + "mean", [mean_euclid_cpm, mean_logeuclid_cpm] +) +def test_mean_cpm_all_equals(optimizer, mean): """Test that the mean of covariance matrices filled with the same value is a matrix identical to the input""" n_trials, n_channels, value = 5, 2, 2.5 covmats = np.full((n_trials, n_channels, n_channels), value) - C = mean_euclid_cpm(covmats) - assert np.allclose(covmats[0], C, atol=0.001) + C = mean(covmats, optimizer=optimizer) + assert np.allclose(covmats[0], C, atol=0.00001) -def test_mean_cpm_mixed(): +@pytest.mark.parametrize( + "optimizer", [ClassicalOptimizer(), NaiveQAOAOptimizer()], +) +@pytest.mark.parametrize( + "mean", [mean_euclid_cpm, mean_logeuclid_cpm] +) +def test_mean_cpm_mixed(optimizer, mean): """Test that the mean of covariances matrices with zero and ones is a matrix filled with 0.5""" n_trials, n_channels = 5, 2 covmats_0 = np.zeros((n_trials, n_channels, n_channels)) covmats_1 = np.ones((n_trials, n_channels, n_channels)) expected_mean = np.full((n_channels, n_channels), 0.5) - C = mean_euclid_cpm(np.concatenate((covmats_0, covmats_1), axis=0)) - assert np.allclose(expected_mean, C, atol=0.001) + C = mean(np.concatenate((covmats_0, covmats_1), axis=0), optimizer=optimizer) + assert np.allclose(expected_mean, C, atol=0.00001) From e84652e5836c3a6d32af67558763ccb6d883f011 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 14:56:12 +0000 Subject: [PATCH 41/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- tests/test_utils_mean.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index ee333201..1ff508bb 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -45,11 +45,10 @@ def test_mean_cpm_shape(get_covmats, mean): @pytest.mark.parametrize( - "optimizer", [ClassicalOptimizer(), NaiveQAOAOptimizer()], -) -@pytest.mark.parametrize( - "mean", [mean_euclid_cpm, mean_logeuclid_cpm] + "optimizer", + [ClassicalOptimizer(), NaiveQAOAOptimizer()], ) +@pytest.mark.parametrize("mean", [mean_euclid_cpm, mean_logeuclid_cpm]) def test_mean_cpm_all_zeros(optimizer, mean): """Test that the mean of covariance matrices containing zeros is a matrix filled with zeros""" @@ -60,11 +59,10 @@ def test_mean_cpm_all_zeros(optimizer, mean): @pytest.mark.parametrize( - "optimizer", [ClassicalOptimizer(), NaiveQAOAOptimizer()], -) -@pytest.mark.parametrize( - "mean", [mean_euclid_cpm, mean_logeuclid_cpm] + "optimizer", + [ClassicalOptimizer(), NaiveQAOAOptimizer()], ) +@pytest.mark.parametrize("mean", [mean_euclid_cpm, mean_logeuclid_cpm]) def test_mean_cpm_all_ones(optimizer, mean): """Test that the mean of covariance matrices containing ones is a matrix filled with ones""" @@ -75,11 +73,10 @@ def test_mean_cpm_all_ones(optimizer, mean): @pytest.mark.parametrize( - "optimizer", [ClassicalOptimizer(), NaiveQAOAOptimizer()], -) -@pytest.mark.parametrize( - "mean", [mean_euclid_cpm, mean_logeuclid_cpm] + "optimizer", + [ClassicalOptimizer(), NaiveQAOAOptimizer()], ) +@pytest.mark.parametrize("mean", [mean_euclid_cpm, mean_logeuclid_cpm]) def test_mean_cpm_all_equals(optimizer, mean): """Test that the mean of covariance matrices filled with the same value is a matrix identical to the input""" @@ -90,11 +87,10 @@ def test_mean_cpm_all_equals(optimizer, mean): @pytest.mark.parametrize( - "optimizer", [ClassicalOptimizer(), NaiveQAOAOptimizer()], -) -@pytest.mark.parametrize( - "mean", [mean_euclid_cpm, mean_logeuclid_cpm] + "optimizer", + [ClassicalOptimizer(), NaiveQAOAOptimizer()], ) +@pytest.mark.parametrize("mean", [mean_euclid_cpm, mean_logeuclid_cpm]) def test_mean_cpm_mixed(optimizer, mean): """Test that the mean of covariances matrices with zero and ones is a matrix filled with 0.5""" From 749d057b100d74fa2389db7ce6770843ddeb5eeb Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Wed, 21 Feb 2024 17:22:51 +0100 Subject: [PATCH 42/90] improvement of tests --- tests/test_utils_mean.py | 51 +++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index 1ff508bb..ab3332bf 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -7,11 +7,16 @@ from sklearn.model_selection import StratifiedKFold, cross_val_score from pyriemann_qiskit.utils.mean import mean_euclid_cpm, mean_logeuclid_cpm from pyriemann_qiskit.utils import ClassicalOptimizer, NaiveQAOAOptimizer +from qiskit_optimization.algorithms import ADMMOptimizer - -def test_performance(get_covmats, get_labels): - metric = {"mean": "euclid_cpm", "distance": "euclid"} - +@pytest.mark.parametrize( + "metric", [ + {"mean": "euclid_cpm", "distance": "euclid"}, + {"mean": "logeuclid_cpm", "distance": "logeuclid"}, + {"mean": "logeuclid", "distance": "logeuclid_cpm"} + ] +) +def test_performance(get_covmats, get_labels, metric): clf = make_pipeline(XdawnCovariances(), MDM(metric=metric)) skf = StratifiedKFold(n_splits=5) n_matrices, n_channels, n_classes = 100, 3, 2 @@ -32,7 +37,7 @@ def test_analytic_vs_cpm_mean(get_covmats, means): covmats = get_covmats(n_trials, n_channels) C = cpm_mean(covmats) C_analytic = analytic_mean(covmats) - assert np.allclose(C, C_analytic, atol=0.001) + assert np.allclose(C, C_analytic, atol=0.00001) @pytest.mark.parametrize("mean", [mean_euclid_cpm, mean_logeuclid_cpm]) @@ -45,52 +50,56 @@ def test_mean_cpm_shape(get_covmats, mean): @pytest.mark.parametrize( - "optimizer", - [ClassicalOptimizer(), NaiveQAOAOptimizer()], + "optimizer", [ClassicalOptimizer(optimizer=ADMMOptimizer()), NaiveQAOAOptimizer()], +) +@pytest.mark.parametrize( + "mean", [mean_euclid_cpm] ) -@pytest.mark.parametrize("mean", [mean_euclid_cpm, mean_logeuclid_cpm]) def test_mean_cpm_all_zeros(optimizer, mean): """Test that the mean of covariance matrices containing zeros is a matrix filled with zeros""" n_trials, n_channels = 5, 2 covmats = np.zeros((n_trials, n_channels, n_channels)) C = mean(covmats, optimizer=optimizer) - assert np.allclose(covmats[0], C, atol=0.00001) + assert np.allclose(covmats[0], C, atol=0.001) @pytest.mark.parametrize( - "optimizer", - [ClassicalOptimizer(), NaiveQAOAOptimizer()], + "optimizer", [ClassicalOptimizer(optimizer=ADMMOptimizer()), NaiveQAOAOptimizer()], +) +@pytest.mark.parametrize( + "mean", [mean_euclid_cpm] ) -@pytest.mark.parametrize("mean", [mean_euclid_cpm, mean_logeuclid_cpm]) def test_mean_cpm_all_ones(optimizer, mean): """Test that the mean of covariance matrices containing ones is a matrix filled with ones""" n_trials, n_channels = 5, 2 covmats = np.ones((n_trials, n_channels, n_channels)) C = mean(covmats, optimizer=optimizer) - assert np.allclose(covmats[0], C, atol=0.00001) + assert np.allclose(covmats[0], C, atol=0.001) @pytest.mark.parametrize( - "optimizer", - [ClassicalOptimizer(), NaiveQAOAOptimizer()], + "optimizer", [ClassicalOptimizer(optimizer=ADMMOptimizer()), NaiveQAOAOptimizer()], +) +@pytest.mark.parametrize( + "mean", [mean_euclid_cpm] ) -@pytest.mark.parametrize("mean", [mean_euclid_cpm, mean_logeuclid_cpm]) def test_mean_cpm_all_equals(optimizer, mean): """Test that the mean of covariance matrices filled with the same value is a matrix identical to the input""" n_trials, n_channels, value = 5, 2, 2.5 covmats = np.full((n_trials, n_channels, n_channels), value) C = mean(covmats, optimizer=optimizer) - assert np.allclose(covmats[0], C, atol=0.00001) + assert np.allclose(covmats[0], C, atol=0.001) @pytest.mark.parametrize( - "optimizer", - [ClassicalOptimizer(), NaiveQAOAOptimizer()], + "optimizer", [ClassicalOptimizer(optimizer=ADMMOptimizer()), NaiveQAOAOptimizer()], +) +@pytest.mark.parametrize( + "mean", [mean_euclid_cpm] ) -@pytest.mark.parametrize("mean", [mean_euclid_cpm, mean_logeuclid_cpm]) def test_mean_cpm_mixed(optimizer, mean): """Test that the mean of covariances matrices with zero and ones is a matrix filled with 0.5""" @@ -99,4 +108,4 @@ def test_mean_cpm_mixed(optimizer, mean): covmats_1 = np.ones((n_trials, n_channels, n_channels)) expected_mean = np.full((n_channels, n_channels), 0.5) C = mean(np.concatenate((covmats_0, covmats_1), axis=0), optimizer=optimizer) - assert np.allclose(expected_mean, C, atol=0.00001) + assert np.allclose(expected_mean, C, atol=0.001) From 7c72b13b4afd554aae7a645ef3b0ce7882c9b6df Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:24:58 +0000 Subject: [PATCH 43/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- tests/test_utils_mean.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index ab3332bf..f9061f59 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -9,12 +9,14 @@ from pyriemann_qiskit.utils import ClassicalOptimizer, NaiveQAOAOptimizer from qiskit_optimization.algorithms import ADMMOptimizer + @pytest.mark.parametrize( - "metric", [ + "metric", + [ {"mean": "euclid_cpm", "distance": "euclid"}, {"mean": "logeuclid_cpm", "distance": "logeuclid"}, - {"mean": "logeuclid", "distance": "logeuclid_cpm"} - ] + {"mean": "logeuclid", "distance": "logeuclid_cpm"}, + ], ) def test_performance(get_covmats, get_labels, metric): clf = make_pipeline(XdawnCovariances(), MDM(metric=metric)) @@ -50,11 +52,10 @@ def test_mean_cpm_shape(get_covmats, mean): @pytest.mark.parametrize( - "optimizer", [ClassicalOptimizer(optimizer=ADMMOptimizer()), NaiveQAOAOptimizer()], -) -@pytest.mark.parametrize( - "mean", [mean_euclid_cpm] + "optimizer", + [ClassicalOptimizer(optimizer=ADMMOptimizer()), NaiveQAOAOptimizer()], ) +@pytest.mark.parametrize("mean", [mean_euclid_cpm]) def test_mean_cpm_all_zeros(optimizer, mean): """Test that the mean of covariance matrices containing zeros is a matrix filled with zeros""" @@ -65,11 +66,10 @@ def test_mean_cpm_all_zeros(optimizer, mean): @pytest.mark.parametrize( - "optimizer", [ClassicalOptimizer(optimizer=ADMMOptimizer()), NaiveQAOAOptimizer()], -) -@pytest.mark.parametrize( - "mean", [mean_euclid_cpm] + "optimizer", + [ClassicalOptimizer(optimizer=ADMMOptimizer()), NaiveQAOAOptimizer()], ) +@pytest.mark.parametrize("mean", [mean_euclid_cpm]) def test_mean_cpm_all_ones(optimizer, mean): """Test that the mean of covariance matrices containing ones is a matrix filled with ones""" @@ -80,11 +80,10 @@ def test_mean_cpm_all_ones(optimizer, mean): @pytest.mark.parametrize( - "optimizer", [ClassicalOptimizer(optimizer=ADMMOptimizer()), NaiveQAOAOptimizer()], -) -@pytest.mark.parametrize( - "mean", [mean_euclid_cpm] + "optimizer", + [ClassicalOptimizer(optimizer=ADMMOptimizer()), NaiveQAOAOptimizer()], ) +@pytest.mark.parametrize("mean", [mean_euclid_cpm]) def test_mean_cpm_all_equals(optimizer, mean): """Test that the mean of covariance matrices filled with the same value is a matrix identical to the input""" @@ -95,11 +94,10 @@ def test_mean_cpm_all_equals(optimizer, mean): @pytest.mark.parametrize( - "optimizer", [ClassicalOptimizer(optimizer=ADMMOptimizer()), NaiveQAOAOptimizer()], -) -@pytest.mark.parametrize( - "mean", [mean_euclid_cpm] + "optimizer", + [ClassicalOptimizer(optimizer=ADMMOptimizer()), NaiveQAOAOptimizer()], ) +@pytest.mark.parametrize("mean", [mean_euclid_cpm]) def test_mean_cpm_mixed(optimizer, mean): """Test that the mean of covariances matrices with zero and ones is a matrix filled with 0.5""" From 13ebae6d4207c60691e19c84e0f25a49f218faa0 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Wed, 21 Feb 2024 17:25:04 +0100 Subject: [PATCH 44/90] fix firestore --- Dockerfile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 021ecc08..464f0abf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,15 +25,15 @@ RUN mkdir /home/mne_data ## Workaround for firestore RUN pip install protobuf==4.25.3 -RUN pip install google_cloud_firestore==2.14.0 +RUN pip install google_cloud_firestore==2.15.0 ### Missing __init__ file in protobuf RUN touch /usr/local/lib/python3.9/site-packages/protobuf-4.25.3-py3.9.egg/google/__init__.py ## google.cloud.location is never used in these files, and is missing in path. -RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.9/site-packages/google_cloud_firestore-2.14.0-py3.9.egg/google/cloud/firestore_v1/services/firestore/client.py' -RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.9/site-packages/google_cloud_firestore-2.14.0-py3.9.egg/google/cloud/firestore_v1/services/firestore/transports/base.py' -RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.9/site-packages/google_cloud_firestore-2.14.0-py3.9.egg/google/cloud/firestore_v1/services/firestore/transports/grpc.py' -RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.9/site-packages/google_cloud_firestore-2.14.0-py3.9.egg/google/cloud/firestore_v1/services/firestore/transports/grpc_asyncio.py' -RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.9/site-packages/google_cloud_firestore-2.14.0-py3.9.egg/google/cloud/firestore_v1/services/firestore/transports/rest.py' -RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.9/site-packages/google_cloud_firestore-2.14.0-py3.9.egg/google/cloud/firestore_v1/services/firestore/async_client.py' +RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.9/site-packages/google_cloud_firestore-2.15.0-py3.9.egg/google/cloud/firestore_v1/services/firestore/client.py' +RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.9/site-packages/google_cloud_firestore-2.15.0-py3.9.egg/google/cloud/firestore_v1/services/firestore/transports/base.py' +RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.9/site-packages/google_cloud_firestore-2.15.0-py3.9.egg/google/cloud/firestore_v1/services/firestore/transports/grpc.py' +RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.9/site-packages/google_cloud_firestore-2.15.0-py3.9.egg/google/cloud/firestore_v1/services/firestore/transports/grpc_asyncio.py' +RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.9/site-packages/google_cloud_firestore-2.15.0-py3.9.egg/google/cloud/firestore_v1/services/firestore/transports/rest.py' +RUN sed -i 's/from google.cloud.location import locations_pb2//g' '/usr/local/lib/python3.9/site-packages/google_cloud_firestore-2.15.0-py3.9.egg/google/cloud/firestore_v1/services/firestore/async_client.py' ENTRYPOINT [ "python", "/examples/ERP/classify_P300_bi.py" ] From 7cdd80b5931d2e91d26b1ce55470ba24dd07f914 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Wed, 21 Feb 2024 17:48:06 +0100 Subject: [PATCH 45/90] add regularization --- tests/test_utils_mean.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index f9061f59..a0545ea5 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -1,25 +1,26 @@ import pytest import numpy as np from pyriemann.utils.mean import mean_euclid, mean_logeuclid -from pyriemann.classification import MDM -from pyriemann.estimation import XdawnCovariances +from pyriemann.estimation import XdawnCovariances, Shrinkage from sklearn.pipeline import make_pipeline from sklearn.model_selection import StratifiedKFold, cross_val_score from pyriemann_qiskit.utils.mean import mean_euclid_cpm, mean_logeuclid_cpm from pyriemann_qiskit.utils import ClassicalOptimizer, NaiveQAOAOptimizer +from pyriemann_qiskit.classification import QuanticMDM from qiskit_optimization.algorithms import ADMMOptimizer @pytest.mark.parametrize( - "metric", + "kernel", [ - {"mean": "euclid_cpm", "distance": "euclid"}, - {"mean": "logeuclid_cpm", "distance": "logeuclid"}, - {"mean": "logeuclid", "distance": "logeuclid_cpm"}, + ({"mean": "euclid_cpm", "distance": "euclid"}, Shrinkage(shrinkage=0.9)), + ({"mean": "logeuclid_cpm", "distance": "logeuclid"}, None), + ({"mean": "logeuclid", "distance": "logeuclid_cpm"}, None), ], ) -def test_performance(get_covmats, get_labels, metric): - clf = make_pipeline(XdawnCovariances(), MDM(metric=metric)) +def test_performance(get_covmats, get_labels, kernel): + metric, regularization = kernel + clf = make_pipeline(XdawnCovariances(), QuanticMDM(metric=metric, regularization=regularization, quantum=False)) skf = StratifiedKFold(n_splits=5) n_matrices, n_channels, n_classes = 100, 3, 2 covset = get_covmats(n_matrices, n_channels) From ecc0351038db9e3ee6f88200bafb191cde371659 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:49:28 +0000 Subject: [PATCH 46/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- tests/test_utils_mean.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index a0545ea5..73ba1b8b 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -20,7 +20,10 @@ ) def test_performance(get_covmats, get_labels, kernel): metric, regularization = kernel - clf = make_pipeline(XdawnCovariances(), QuanticMDM(metric=metric, regularization=regularization, quantum=False)) + clf = make_pipeline( + XdawnCovariances(), + QuanticMDM(metric=metric, regularization=regularization, quantum=False), + ) skf = StratifiedKFold(n_splits=5) n_matrices, n_channels, n_classes = 100, 3, 2 covset = get_covmats(n_matrices, n_channels) From f1f0c3e37bdc62348717829fcbbeddd72091ca4f Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Wed, 21 Feb 2024 21:20:42 +0100 Subject: [PATCH 47/90] add missing regularization --- tests/test_utils_mean.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index 73ba1b8b..6074b380 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -14,8 +14,8 @@ "kernel", [ ({"mean": "euclid_cpm", "distance": "euclid"}, Shrinkage(shrinkage=0.9)), - ({"mean": "logeuclid_cpm", "distance": "logeuclid"}, None), - ({"mean": "logeuclid", "distance": "logeuclid_cpm"}, None), + ({"mean": "logeuclid_cpm", "distance": "logeuclid"}, Shrinkage(shrinkage=0.9)), + ({"mean": "logeuclid", "distance": "logeuclid_cpm"}, Shrinkage(shrinkage=0.9)), ], ) def test_performance(get_covmats, get_labels, kernel): From c8923bb1f468ec0bfe45021b603fd189c8ad280e Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Wed, 21 Feb 2024 22:34:17 +0100 Subject: [PATCH 48/90] fix tests --- tests/test_utils_mean.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index 6074b380..2e61de4e 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -14,8 +14,8 @@ "kernel", [ ({"mean": "euclid_cpm", "distance": "euclid"}, Shrinkage(shrinkage=0.9)), - ({"mean": "logeuclid_cpm", "distance": "logeuclid"}, Shrinkage(shrinkage=0.9)), - ({"mean": "logeuclid", "distance": "logeuclid_cpm"}, Shrinkage(shrinkage=0.9)), + ({"mean": "euclid", "distance": "logeuclid_cpm"}, None), + ({"mean": "logeuclid_cpm", "distance": "euclid"}, None), ], ) def test_performance(get_covmats, get_labels, kernel): From 9ee3473f81536d961adb0dcde3450350075b7a15 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Wed, 21 Feb 2024 22:54:42 +0100 Subject: [PATCH 49/90] just remove logeuclid_cpm for the moment for test performance. The covset is not well formed, and even logeuclid_mean fails. --- tests/test_utils_mean.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index 2e61de4e..c0df1b12 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -15,7 +15,6 @@ [ ({"mean": "euclid_cpm", "distance": "euclid"}, Shrinkage(shrinkage=0.9)), ({"mean": "euclid", "distance": "logeuclid_cpm"}, None), - ({"mean": "logeuclid_cpm", "distance": "euclid"}, None), ], ) def test_performance(get_covmats, get_labels, kernel): From 284dc864c6f8e0dd579fc30e599c8845e2f5a103 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Thu, 22 Feb 2024 10:06:10 +0100 Subject: [PATCH 50/90] - change the behavior of the metric parameter in pipeline so it is the same as for MDM - expose optimizer and regularization at pipeline level - add is_cpm_mean and is_cpm_dist function - fix behavior of votingclassifier, and set it back to this default behavior (a mix of euclidian and logeuclian MDM) - fix benchmark script --- benchmarks/light_benchmark.py | 8 +++-- pyriemann_qiskit/classification.py | 10 +++++- pyriemann_qiskit/pipelines.py | 53 ++++++++++++++++++------------ pyriemann_qiskit/utils/__init__.py | 6 ++-- pyriemann_qiskit/utils/distance.py | 26 +++++++++++++++ pyriemann_qiskit/utils/mean.py | 26 +++++++++++++++ 6 files changed, 103 insertions(+), 26 deletions(-) diff --git a/benchmarks/light_benchmark.py b/benchmarks/light_benchmark.py index da119a04..08067586 100644 --- a/benchmarks/light_benchmark.py +++ b/benchmarks/light_benchmark.py @@ -81,10 +81,14 @@ shots=100, spsa_trials=5, two_local_reps=2, params={"seed": 42} ) -pipelines["QMDM_mean"] = QuantumMDMWithRiemannianPipeline(metric="mean", quantum=True) +pipelines["QMDM_mean"] = QuantumMDMWithRiemannianPipeline( + metric={'mean': 'euclid_cpm', 'distance': 'euclid'}, + quantum=True +) pipelines["QMDM_dist"] = QuantumMDMWithRiemannianPipeline( - metric="distance", quantum=True + metric={'mean': 'logeuclid', 'distance': 'logeuclid_cpm'}, + quantum=True ) pipelines["RG_LDA"] = make_pipeline( diff --git a/pyriemann_qiskit/classification.py b/pyriemann_qiskit/classification.py index 2eea526a..aac9818f 100644 --- a/pyriemann_qiskit/classification.py +++ b/pyriemann_qiskit/classification.py @@ -21,6 +21,7 @@ from qiskit_ibm_provider import IBMProvider, least_busy from qiskit_machine_learning.algorithms import QSVC, VQC, PegasosQSVC from qiskit_machine_learning.kernels.quantum_kernel import QuantumKernel +from qiskit_optimization.algorithms import CobylaOptimizer from sklearn.base import BaseEstimator, ClassifierMixin from sklearn.svm import SVC @@ -595,6 +596,7 @@ class QuanticMDM(QuanticClassifierBase): .. versionchanged:: 0.2.0 Add seed parameter. Add regularization parameter. + Add classical_optimizer parameter. Parameters ---------- @@ -627,6 +629,8 @@ class QuanticMDM(QuanticClassifierBase): The maximum integer value for matrix normalization. regularization: MixinTransformer (defulat: None) Additional post-processing to regularize means. + classical_optimizer : OptimizationAlgorithm + An instance of OptimizationAlgorithm [3]_ See Also -------- @@ -645,6 +649,8 @@ class QuanticMDM(QuanticClassifierBase): A. Barachant, S. Bonnet, M. Congedo and C. Jutten. 9th International Conference Latent Variable Analysis and Signal Separation (LVA/ICA 2010), LNCS vol. 6365, 2010, p. 629-636. + .. [3] \ + https://qiskit-community.github.io/qiskit-optimization/stubs/qiskit_optimization.algorithms.OptimizationAlgorithm.html#optimizationalgorithm """ def __init__( @@ -657,6 +663,7 @@ def __init__( seed=None, upper_bound=7, regularization=None, + classical_optimizer=CobylaOptimizer(rhobeg=2.1, rhoend=0.000001) ): QuanticClassifierBase.__init__( self, quantum, q_account_token, verbose, shots, None, seed @@ -664,6 +671,7 @@ def __init__( self.metric = metric self.upper_bound = upper_bound self.regularization = regularization + self.classical_optimizer = classical_optimizer def _init_algo(self, n_features): self._log("Quantic MDM initiating algorithm") @@ -675,7 +683,7 @@ def _init_algo(self, n_features): ) else: self._log("Using ClassicalOptimizer (COBYLA)") - self._optimizer = ClassicalOptimizer() + self._optimizer = ClassicalOptimizer(self.classical_optimizer) set_global_optimizer(self._optimizer) return classifier diff --git a/pyriemann_qiskit/pipelines.py b/pyriemann_qiskit/pipelines.py index eb391054..232e872e 100644 --- a/pyriemann_qiskit/pipelines.py +++ b/pyriemann_qiskit/pipelines.py @@ -4,9 +4,11 @@ from sklearn.decomposition import PCA from sklearn.pipeline import make_pipeline from sklearn.ensemble import VotingClassifier +from qiskit_optimization.algorithms import CobylaOptimizer from pyriemann.estimation import XdawnCovariances, ERPCovariances from pyriemann.tangentspace import TangentSpace from pyriemann.preprocessing import Whitening +from pyriemann_qiskit.utils.mean import is_cpm_mean from pyriemann_qiskit.utils.filtering import NoDimRed from pyriemann_qiskit.utils.hyper_params_factory import ( # gen_zz_feature_map, @@ -310,14 +312,8 @@ class QuantumMDMWithRiemannianPipeline(BasePipeline): Parameters ---------- - metric : string (default: "distance") - `metric` passed to the inner QuanticMDM depends on the - `metric` as follows (metric => metric): - - - "distance" => {mean=logeuclid, distance=logeuclid_cpm}, - - "mean" => {mean=logeuclid_cpm, distance=logeuclid}, - - "both" => {mean=logeuclid_cpm, distance=logeuclid_cpm}, - - other => same as "distance". + metric : string | dict, default={"mean": 'logeuclid', "distance": 'logeuclid_cpm'} + The type of metric used for centroid and distance estimation. quantum : bool (default: True) - If true will run on local or remote backend (depending on q_account_token value), @@ -333,6 +329,10 @@ class QuantumMDMWithRiemannianPipeline(BasePipeline): Number of repetitions of each circuit, for sampling. upper_bound : int (default: 7) The maximum integer value for matrix normalization. + regularization: MixinTransformer (defulat: None) + Additional post-processing to regularize means. + classical_optimizer : OptimizationAlgorithm + An instance of OptimizationAlgorithm [1]_ Attributes ---------- @@ -342,21 +342,33 @@ class QuantumMDMWithRiemannianPipeline(BasePipeline): Notes ----- .. versionadded:: 0.1.0 + .. versionchanged:: 0.2.0 + Add regularization parameter. + Add classical_optimizer parameter. + Change metric, so you can pass the kernel of your choice\ + as when using MDM. See Also -------- QuanticMDM + References + ---------- + .. [1] \ + https://qiskit-community.github.io/qiskit-optimization/stubs/qiskit_optimization.algorithms.OptimizationAlgorithm.html#optimizationalgorithm + """ def __init__( self, - metric="distance", + metric={'mean': 'logeuclid', 'distance': 'logeuclid_cpm'}, quantum=True, q_account_token=None, verbose=True, shots=1024, upper_bound=7, + regularization=None, + classical_optimizer=CobylaOptimizer(rhobeg=2.1, rhoend=0.000001) ): self.metric = metric self.quantum = quantum @@ -364,18 +376,15 @@ def __init__( self.verbose = verbose self.shots = shots self.upper_bound = upper_bound + self.regularization = regularization + self.classical_optimizer = classical_optimizer BasePipeline.__init__(self, "QuantumMDMWithRiemannianPipeline") def _create_pipe(self): - if self.metric == "both": - metric = {"mean": "logeuclid_cpm", "distance": "logeuclid_cpm"} - elif self.metric == "mean": - metric = {"mean": "logeuclid_cpm", "distance": "logeuclid"} - else: - metric = {"mean": "logeuclid", "distance": "logeuclid_cpm"} - - if metric["mean"] == "logeuclid_cpm": + print(self.metric) + print(self.metric['mean']) + if is_cpm_mean(self.metric["mean"]): if self.quantum: covariances = XdawnCovariances( nfilter=1, estimator="scm", xdawn_estimator="lwf" @@ -389,12 +398,14 @@ def _create_pipe(self): filtering = NoDimRed() clf = QuanticMDM( - metric=metric, + metric=self.metric, quantum=self.quantum, q_account_token=self.q_account_token, verbose=self.verbose, shots=self.shots, upper_bound=self.upper_bound, + regularization=self.regularization, + classical_optimizer=self.classical_optimizer ) return make_pipeline(covariances, filtering, clf) @@ -407,7 +418,7 @@ class QuantumMDMVotingClassifier(BasePipeline): Voting classifier with two configurations of QuantumMDMWithRiemannianPipeline: - - with mean = logeuclid_cpm and distance = logeuclid, + - with mean = euclid_cpm and distance = euclid, - with mean = logeuclid and distance = logeuclid_cpm. Parameters @@ -461,7 +472,7 @@ def __init__( def _create_pipe(self): clf_mean_logeuclid_dist_cpm = QuantumMDMWithRiemannianPipeline( - "distance", + {'mean': 'logeuclid', 'distance': 'logeuclid_cpm'}, self.quantum, self.q_account_token, self.verbose, @@ -469,7 +480,7 @@ def _create_pipe(self): self.upper_bound, ) clf_mean_cpm_dist_euclid = QuantumMDMWithRiemannianPipeline( - "mean", + {'mean': 'euclid_cpm', 'distance' : 'euclid'}, self.quantum, self.q_account_token, self.verbose, diff --git a/pyriemann_qiskit/utils/__init__.py b/pyriemann_qiskit/utils/__init__.py index fa63aabc..b4704ce1 100644 --- a/pyriemann_qiskit/utils/__init__.py +++ b/pyriemann_qiskit/utils/__init__.py @@ -18,7 +18,8 @@ add_moabb_dataframe_results_to_caches, convert_caches_to_dataframes, ) -from .distance import distance_logeuclid_cpm +from . import distance +from . import mean __all__ = [ "hyper_params_factory", @@ -36,7 +37,8 @@ "NaiveQAOAOptimizer", "set_global_optimizer", "get_global_optimizer", - "distance_logeuclid_cpm", + "distance", + "mean", "FirebaseConnector", "Cache", "generate_caches", diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index c7d08098..d3fe9cf9 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -89,6 +89,32 @@ def predict_distances(mdm, X): return _mdm_predict_distances_original(mdm, X) +def is_cpm_dist(string): + """Return True is "string" represents a Constraint Programming Model (CPM) [1]_ + distance available in the library. + + Parameters + ---------- + string: str + A string representation of the distance. + + Returns + ------- + is_cpm_dist : boolean + True if "string" represents a CPM distance aailable in the library. + + Notes + ----- + .. versionadded:: 0.2.0 + + References + ---------- + .. [1] \ + http://ibmdecisionoptimization.github.io/docplex-doc/cp/creating_model.html + + """ + return "_cpm" in string and string in distance_functions + MDM._predict_distances = predict_distances # This is only for validation inside the MDM. diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 87f983ba..a3f29d56 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -117,5 +117,31 @@ def mean_logeuclid_cpm( return expm(result) +def is_cpm_mean(string): + """Return True is "string" represents a Constraint Programming Model (CPM) [1]_ + mean available in the library. + + Parameters + ---------- + string: str + A string representation of the mean. + + Returns + ------- + is_cpm_mean : boolean + True if "string" represents a CPM mean aailable in the library. + + Notes + ----- + .. versionadded:: 0.2.0 + + References + ---------- + .. [1] \ + http://ibmdecisionoptimization.github.io/docplex-doc/cp/creating_model.html + + """ + return "_cpm" in string and string in mean_functions + mean_functions["euclid_cpm"] = mean_euclid_cpm mean_functions["logeuclid_cpm"] = mean_logeuclid_cpm From 716aad6eabe12838394d60940fbc9fcc460bc77a Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Thu, 22 Feb 2024 10:21:19 +0100 Subject: [PATCH 51/90] add regularization to light_benchmark --- benchmarks/light_benchmark.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/benchmarks/light_benchmark.py b/benchmarks/light_benchmark.py index 08067586..26ddcf15 100644 --- a/benchmarks/light_benchmark.py +++ b/benchmarks/light_benchmark.py @@ -11,7 +11,7 @@ # Modified from plot_classify_P300_bi.py of pyRiemann # License: BSD (3-clause) -from pyriemann.estimation import XdawnCovariances +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 @@ -83,7 +83,8 @@ pipelines["QMDM_mean"] = QuantumMDMWithRiemannianPipeline( metric={'mean': 'euclid_cpm', 'distance': 'euclid'}, - quantum=True + quantum=True, + regularization=Shrinkage(shrinkage=0.9) ) pipelines["QMDM_dist"] = QuantumMDMWithRiemannianPipeline( From 8280e93c2b8cae8910a51c73cb77fab1600bcfc7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 09:22:20 +0000 Subject: [PATCH 52/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- benchmarks/light_benchmark.py | 7 +++---- pyriemann_qiskit/classification.py | 2 +- pyriemann_qiskit/pipelines.py | 12 ++++++------ pyriemann_qiskit/utils/distance.py | 3 ++- pyriemann_qiskit/utils/mean.py | 3 ++- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/benchmarks/light_benchmark.py b/benchmarks/light_benchmark.py index 26ddcf15..23b44ca4 100644 --- a/benchmarks/light_benchmark.py +++ b/benchmarks/light_benchmark.py @@ -82,14 +82,13 @@ ) pipelines["QMDM_mean"] = QuantumMDMWithRiemannianPipeline( - metric={'mean': 'euclid_cpm', 'distance': 'euclid'}, + metric={"mean": "euclid_cpm", "distance": "euclid"}, quantum=True, - regularization=Shrinkage(shrinkage=0.9) + regularization=Shrinkage(shrinkage=0.9), ) pipelines["QMDM_dist"] = QuantumMDMWithRiemannianPipeline( - metric={'mean': 'logeuclid', 'distance': 'logeuclid_cpm'}, - quantum=True + metric={"mean": "logeuclid", "distance": "logeuclid_cpm"}, quantum=True ) pipelines["RG_LDA"] = make_pipeline( diff --git a/pyriemann_qiskit/classification.py b/pyriemann_qiskit/classification.py index aac9818f..98cf4df6 100644 --- a/pyriemann_qiskit/classification.py +++ b/pyriemann_qiskit/classification.py @@ -663,7 +663,7 @@ def __init__( seed=None, upper_bound=7, regularization=None, - classical_optimizer=CobylaOptimizer(rhobeg=2.1, rhoend=0.000001) + classical_optimizer=CobylaOptimizer(rhobeg=2.1, rhoend=0.000001), ): QuanticClassifierBase.__init__( self, quantum, q_account_token, verbose, shots, None, seed diff --git a/pyriemann_qiskit/pipelines.py b/pyriemann_qiskit/pipelines.py index 232e872e..fb4ac338 100644 --- a/pyriemann_qiskit/pipelines.py +++ b/pyriemann_qiskit/pipelines.py @@ -361,14 +361,14 @@ class QuantumMDMWithRiemannianPipeline(BasePipeline): def __init__( self, - metric={'mean': 'logeuclid', 'distance': 'logeuclid_cpm'}, + metric={"mean": "logeuclid", "distance": "logeuclid_cpm"}, quantum=True, q_account_token=None, verbose=True, shots=1024, upper_bound=7, regularization=None, - classical_optimizer=CobylaOptimizer(rhobeg=2.1, rhoend=0.000001) + classical_optimizer=CobylaOptimizer(rhobeg=2.1, rhoend=0.000001), ): self.metric = metric self.quantum = quantum @@ -383,7 +383,7 @@ def __init__( def _create_pipe(self): print(self.metric) - print(self.metric['mean']) + print(self.metric["mean"]) if is_cpm_mean(self.metric["mean"]): if self.quantum: covariances = XdawnCovariances( @@ -405,7 +405,7 @@ def _create_pipe(self): shots=self.shots, upper_bound=self.upper_bound, regularization=self.regularization, - classical_optimizer=self.classical_optimizer + classical_optimizer=self.classical_optimizer, ) return make_pipeline(covariances, filtering, clf) @@ -472,7 +472,7 @@ def __init__( def _create_pipe(self): clf_mean_logeuclid_dist_cpm = QuantumMDMWithRiemannianPipeline( - {'mean': 'logeuclid', 'distance': 'logeuclid_cpm'}, + {"mean": "logeuclid", "distance": "logeuclid_cpm"}, self.quantum, self.q_account_token, self.verbose, @@ -480,7 +480,7 @@ def _create_pipe(self): self.upper_bound, ) clf_mean_cpm_dist_euclid = QuantumMDMWithRiemannianPipeline( - {'mean': 'euclid_cpm', 'distance' : 'euclid'}, + {"mean": "euclid_cpm", "distance": "euclid"}, self.quantum, self.q_account_token, self.verbose, diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index d3fe9cf9..6f11faf1 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -90,7 +90,7 @@ def predict_distances(mdm, X): def is_cpm_dist(string): - """Return True is "string" represents a Constraint Programming Model (CPM) [1]_ + """Return True is "string" represents a Constraint Programming Model (CPM) [1]_ distance available in the library. Parameters @@ -115,6 +115,7 @@ def is_cpm_dist(string): """ return "_cpm" in string and string in distance_functions + MDM._predict_distances = predict_distances # This is only for validation inside the MDM. diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index a3f29d56..2725cf0b 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -118,7 +118,7 @@ def mean_logeuclid_cpm( def is_cpm_mean(string): - """Return True is "string" represents a Constraint Programming Model (CPM) [1]_ + """Return True is "string" represents a Constraint Programming Model (CPM) [1]_ mean available in the library. Parameters @@ -143,5 +143,6 @@ def is_cpm_mean(string): """ return "_cpm" in string and string in mean_functions + mean_functions["euclid_cpm"] = mean_euclid_cpm mean_functions["logeuclid_cpm"] = mean_logeuclid_cpm From 0c0ea9952d2c0b4a7eff89583bff4dd72c54e218 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Thu, 22 Feb 2024 10:43:51 +0100 Subject: [PATCH 53/90] fix import in test --- tests/test_utils_distance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_utils_distance.py b/tests/test_utils_distance.py index 1c23a03a..3cecc757 100644 --- a/tests/test_utils_distance.py +++ b/tests/test_utils_distance.py @@ -3,8 +3,8 @@ from pyriemann_qiskit.utils import ( ClassicalOptimizer, NaiveQAOAOptimizer, - distance_logeuclid_cpm, ) +from pyriemann_qiskit.utils.distance import distance_logeuclid_cpm from pyriemann_qiskit.datasets import get_mne_sample from pyriemann.classification import MDM from pyriemann.estimation import XdawnCovariances From 5cf8ff053ac842334b65cc6f58917dbcc87635cd Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Thu, 22 Feb 2024 12:19:06 +0100 Subject: [PATCH 54/90] lint --- pyriemann_qiskit/utils/distance.py | 6 ++++-- pyriemann_qiskit/utils/mean.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 6f11faf1..6f3f5889 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -18,7 +18,8 @@ def logeucl_dist_convex(): def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer()): """Log-Euclidean distance by Constraint Programming Model. - Constraint Programming Model (CPM) [2]_ formulation of the Log-Euclidean distance [1]_. + Constraint Programming Model (CPM) [2]_ formulation of + the Log-Euclidean distance [1]_. Parameters ---------- @@ -44,7 +45,8 @@ def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer()): ---------- .. [1] \ K. Zhao, A. Wiliem, S. Chen, and B. C. Lovell, - ‘Convex Class Model on Symmetric Positive Definite Manifolds’, Image and Vision Computing, 2019. + ‘Convex Class Model on Symmetric Positive Definite Manifolds’, + Image and Vision Computing, 2019. .. [2] \ http://ibmdecisionoptimization.github.io/docplex-doc/cp/creating_model.html diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 2725cf0b..0d899c13 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -4,7 +4,6 @@ from pyriemann_qiskit.utils.docplex import ClassicalOptimizer, get_global_optimizer from pyriemann.utils.base import logm, expm from qiskit_optimization.algorithms import ADMMOptimizer -import numpy as np @deprecated( @@ -104,8 +103,9 @@ def mean_logeuclid_cpm( References ---------- .. [1] \ - Geometric means in a novel vector space structure on symmetric positive-definite matrices\ - V. Arsigny, P. Fillard, X. Pennec, and N. Ayache.\ + Geometric means in a novel vector space structure on + symmetric positive-definite matrices + V. Arsigny, P. Fillard, X. Pennec, and N. Ayache. SIAM Journal on Matrix Analysis and Applications. Volume 29, Issue 1 (2007). .. [2] \ http://ibmdecisionoptimization.github.io/docplex-doc/cp/creating_model.html From c8079f74cd594254991dde2297b7b6c5cc688e4e Mon Sep 17 00:00:00 2001 From: gcattan Date: Thu, 22 Feb 2024 21:41:02 +0100 Subject: [PATCH 55/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 6f3f5889..578e60e2 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -103,7 +103,7 @@ def is_cpm_dist(string): Returns ------- is_cpm_dist : boolean - True if "string" represents a CPM distance aailable in the library. + True if "string" represents a CPM distance available in the library. Notes ----- From 2d0e8abefb32a41eee3db01580df77c3b6bd272c Mon Sep 17 00:00:00 2001 From: gcattan Date: Thu, 22 Feb 2024 21:41:32 +0100 Subject: [PATCH 56/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 578e60e2..6a3d03b9 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -26,7 +26,7 @@ def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer()): A : ndarray, shape (n_classes, n_channels, n_channels) Set of SPD matrices. B : ndarray, shape (n_channels, n_channels) - A trial + SPD matrix. optimizer: pyQiskitOptimizer An instance of pyQiskitOptimizer. From 548e32d16c3f8a290dbcc69576718d49510ce3af Mon Sep 17 00:00:00 2001 From: gcattan Date: Thu, 22 Feb 2024 21:42:05 +0100 Subject: [PATCH 57/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 6a3d03b9..23b63b33 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -54,8 +54,8 @@ def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer()): optimizer = get_global_optimizer(optimizer) - n_classes, _, _ = A.shape - classes = range(n_classes) + n_matrices, _, _ = A.shape + matrices = range(n_matrices) def log_prod(m1, m2): return np.nansum(logm(m1).flatten() * logm(m2).flatten()) From 8cde304cb1bc6acb33a3078463df52aea6a0c956 Mon Sep 17 00:00:00 2001 From: gcattan Date: Thu, 22 Feb 2024 21:45:58 +0100 Subject: [PATCH 58/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 23b63b33..96be9635 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -65,7 +65,7 @@ def log_prod(m1, m2): # should be part of the optimizer w = optimizer.get_weights(prob, classes) - _2VecLogYD = 2 * prob.sum(w[i] * log_prod(B, A[i]) for i in classes) + _2VecLogYD = 2 * prob.sum(w[i] * log_prod(B, A[i]) for i in matrices) wtDw = prob.sum( w[i] * w[j] * log_prod(A[i], A[j]) for i in classes for j in classes From 1be5a3de01d766b1ffeec26d515d99937cf64a9b Mon Sep 17 00:00:00 2001 From: gcattan Date: Thu, 22 Feb 2024 21:46:08 +0100 Subject: [PATCH 59/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 96be9635..f1e03a79 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -68,7 +68,7 @@ def log_prod(m1, m2): _2VecLogYD = 2 * prob.sum(w[i] * log_prod(B, A[i]) for i in matrices) wtDw = prob.sum( - w[i] * w[j] * log_prod(A[i], A[j]) for i in classes for j in classes + w[i] * w[j] * log_prod(A[i], A[j]) for i in matrices for j in matrices ) objectives = wtDw - _2VecLogYD From b69854e46c3d8cbaf53eb41122be39d2ae00fd1e Mon Sep 17 00:00:00 2001 From: gcattan Date: Thu, 22 Feb 2024 21:46:21 +0100 Subject: [PATCH 60/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index f1e03a79..5598c1a8 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -16,10 +16,11 @@ def logeucl_dist_convex(): def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer()): - """Log-Euclidean distance by Constraint Programming Model. + """Log-Euclidean distance to a convex hull of SPD matrices. - Constraint Programming Model (CPM) [2]_ formulation of - the Log-Euclidean distance [1]_. + Log-Euclidean distance between a SPD matrix B and the convex hull of a set + of SPD matrices A [1]_, formulated as a Constraint Programming Model (CPM) + [2]_. Parameters ---------- From b082823d33bd702ba7244bf2c1a8c840b1a06571 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Thu, 22 Feb 2024 22:00:19 +0100 Subject: [PATCH 61/90] Improve doc --- pyriemann_qiskit/utils/distance.py | 15 ++++++++------- pyriemann_qiskit/utils/mean.py | 4 +++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 5598c1a8..96bf3981 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -24,7 +24,7 @@ def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer()): Parameters ---------- - A : ndarray, shape (n_classes, n_channels, n_channels) + A : ndarray, shape (n_matrices, n_channels, n_channels) Set of SPD matrices. B : ndarray, shape (n_channels, n_channels) SPD matrix. @@ -33,10 +33,9 @@ def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer()): Returns ------- - weights : ndarray, shape (n_classes,) - The weights associated with each class. - Higher the weight, closer it is to the class prototype. - Weights are not normalized. + weights : ndarray, shape (n_matrices,) + The optimized weight for each SPD matrix in A, allowing the construction + of the convex hull. Notes ----- @@ -64,7 +63,7 @@ def log_prod(m1, m2): prob = Model() # should be part of the optimizer - w = optimizer.get_weights(prob, classes) + w = optimizer.get_weights(prob, matrices) _2VecLogYD = 2 * prob.sum(w[i] * log_prod(B, A[i]) for i in matrices) @@ -93,7 +92,9 @@ def predict_distances(mdm, X): def is_cpm_dist(string): - """Return True is "string" represents a Constraint Programming Model (CPM) [1]_ + """Indicates if the distance is a CPM distance. + + Return True is "string" represents a Constraint Programming Model (CPM) [1]_ distance available in the library. Parameters diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 0d899c13..77b45131 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -118,7 +118,9 @@ def mean_logeuclid_cpm( def is_cpm_mean(string): - """Return True is "string" represents a Constraint Programming Model (CPM) [1]_ + """Indicates if the mean is a CPM mean. + + Return True is "string" represents a Constraint Programming Model (CPM) [1]_ mean available in the library. Parameters From 633a6bda20d5700ea010403620970491ca72b2a3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 21:01:30 +0000 Subject: [PATCH 62/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- pyriemann_qiskit/utils/distance.py | 2 +- pyriemann_qiskit/utils/mean.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 96bf3981..ea534c2d 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -93,7 +93,7 @@ def predict_distances(mdm, X): def is_cpm_dist(string): """Indicates if the distance is a CPM distance. - + Return True is "string" represents a Constraint Programming Model (CPM) [1]_ distance available in the library. diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 77b45131..5fa26026 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -119,7 +119,7 @@ def mean_logeuclid_cpm( def is_cpm_mean(string): """Indicates if the mean is a CPM mean. - + Return True is "string" represents a Constraint Programming Model (CPM) [1]_ mean available in the library. From 8f5aeab30ec8723209f87b8d4afe5678e06bd6d5 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Fri, 23 Feb 2024 08:50:20 +0100 Subject: [PATCH 63/90] diminish number of trial for SPSA in benchmark script --- benchmarks/light_benchmark.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/light_benchmark.py b/benchmarks/light_benchmark.py index 23b44ca4..9ad973e8 100644 --- a/benchmarks/light_benchmark.py +++ b/benchmarks/light_benchmark.py @@ -78,7 +78,7 @@ ) pipelines["RG_VQC"] = QuantumClassifierWithDefaultRiemannianPipeline( - shots=100, spsa_trials=5, two_local_reps=2, params={"seed": 42} + shots=100, spsa_trials=1, two_local_reps=2, params={"seed": 42} ) pipelines["QMDM_mean"] = QuantumMDMWithRiemannianPipeline( From b6057f34da4bbe36d4a3b17ea26e1da4bbc1b3ab Mon Sep 17 00:00:00 2001 From: qbarthelemy Date: Fri, 23 Feb 2024 12:11:18 +0100 Subject: [PATCH 64/90] improve mean --- pyriemann_qiskit/utils/mean.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/pyriemann_qiskit/utils/mean.py b/pyriemann_qiskit/utils/mean.py index 5fa26026..e169efd0 100644 --- a/pyriemann_qiskit/utils/mean.py +++ b/pyriemann_qiskit/utils/mean.py @@ -14,7 +14,7 @@ def fro_mean_convex(): pass -def mean_euclid_cpm(covmats, sample_weight=None, optimizer=ClassicalOptimizer()): +def mean_euclid_cpm(X, sample_weight=None, optimizer=ClassicalOptimizer()): """Euclidean mean with Constraint Programming Model. Constraint Programming Model (CPM) [1]_ formulation of the mean @@ -22,12 +22,12 @@ def mean_euclid_cpm(covmats, sample_weight=None, optimizer=ClassicalOptimizer()) Parameters ---------- - covmats: ndarray, shape (n_matrices, n_channels, n_channels) + X : ndarray, shape (n_matrices, n_channels, n_channels) Set of SPD matrices. - sample_weights: None | ndarray, shape (n_matrices,), default=None + sample_weights : None | ndarray, shape (n_matrices,), default=None Weights for each matrix. Never used in practice. It is kept only for standardization with pyRiemann. - optimizer: pyQiskitOptimizer + optimizer : pyQiskitOptimizer An instance of pyQiskitOptimizer. Returns @@ -52,19 +52,19 @@ def mean_euclid_cpm(covmats, sample_weight=None, optimizer=ClassicalOptimizer()) optimizer = get_global_optimizer(optimizer) - n_trials, n_channels, _ = covmats.shape + n_matrices, n_channels, _ = X.shape channels = range(n_channels) - trials = range(n_trials) + matrices = range(n_matrices) prob = Model() X_mean = optimizer.covmat_var(prob, channels, "fro_mean") - def _fro_dist(A, B): + def _dist_euclid(A, B): A = optimizer.convert_covmat(A) return prob.sum_squares(A[r, c] - B[r, c] for r in channels for c in channels) - objectives = prob.sum(_fro_dist(covmats[i], X_mean) for i in trials) + objectives = prob.sum(_dist_euclid(X[i], X_mean) for i in matrices) prob.set_objective("min", objectives) @@ -78,17 +78,17 @@ def mean_logeuclid_cpm( ): """Log-Euclidean mean with Constraint Programming Model. - Constraint Programming Model (CPM) [2]_ formulation of the mean + Constraint Programming Model (CPM) [2]_ formulation of the mean with Log-Euclidean distance [1]_. Parameters ---------- - X: ndarray, shape (n_matrices, n_channels, n_channels) + X : ndarray, shape (n_matrices, n_channels, n_channels) Set of SPD matrices. - sample_weights: None | ndarray, shape (n_matrices,), default=None + sample_weights : None | ndarray, shape (n_matrices,), default=None Weights for each matrix. Never used in practice. It is kept only for standardization with pyRiemann. - optimizer: pyQiskitOptimizer + optimizer : pyQiskitOptimizer An instance of pyQiskitOptimizer. Returns @@ -109,7 +109,6 @@ def mean_logeuclid_cpm( SIAM Journal on Matrix Analysis and Applications. Volume 29, Issue 1 (2007). .. [2] \ http://ibmdecisionoptimization.github.io/docplex-doc/cp/creating_model.html - """ log_X = logm(X) From 2145c49637cbcd7c9b5dbfe526be661f6a61d206 Mon Sep 17 00:00:00 2001 From: gcattan Date: Fri, 23 Feb 2024 12:51:12 +0100 Subject: [PATCH 65/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index ea534c2d..5242e4f5 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -34,8 +34,9 @@ def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer()): Returns ------- weights : ndarray, shape (n_matrices,) - The optimized weight for each SPD matrix in A, allowing the construction - of the convex hull. + The optimized weights for the set of SPD matrices A. + Using these weights, the weighted Log-Euclidean mean of set A + provides the matrix of the convex hull closest to matrix B. Notes ----- From 53bfcea1343b7b44907cabe633e65270f3d03882 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Fri, 23 Feb 2024 18:35:17 +0100 Subject: [PATCH 66/90] add distance output --- pyriemann_qiskit/utils/distance.py | 15 +++++++++++++-- tests/test_utils_distance.py | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 5242e4f5..b3ed406e 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -37,6 +37,10 @@ def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer()): The optimized weights for the set of SPD matrices A. Using these weights, the weighted Log-Euclidean mean of set A provides the matrix of the convex hull closest to matrix B. + distance : float + Log-Euclidean distance between the SPD matrix B and the convex hull of the set + of SPD matrices A, defined as the distance between B and the matrix of the convex + hull closest to matrix B. Notes ----- @@ -78,7 +82,14 @@ def log_prod(m1, m2): result = optimizer.solve(prob, reshape=False) - return 1 - result + # compute distance + _2VecLogYD = 2 * sum(result[i] * log_prod(B, A[i]) for i in matrices) + wtDw = sum( + result[i] * result[j] * log_prod(A[i], A[j]) for i in matrices for j in matrices + ) + distance = wtDw - _2VecLogYD + + return 1 - result, distance _mdm_predict_distances_original = MDM._predict_distances @@ -87,7 +98,7 @@ def log_prod(m1, m2): def predict_distances(mdm, X): if mdm.metric_dist == "logeuclid_cpm": centroids = np.array(mdm.covmeans_) - return np.array([distance_logeuclid_cpm(centroids, x) for x in X]) + return np.array([distance_logeuclid_cpm(centroids, x)[0] for x in X]) else: return _mdm_predict_distances_original(mdm, X) diff --git a/tests/test_utils_distance.py b/tests/test_utils_distance.py index 3cecc757..e62a1a75 100644 --- a/tests/test_utils_distance.py +++ b/tests/test_utils_distance.py @@ -28,5 +28,5 @@ def test_distance_logeuclid_cpm(optimizer): X_1 = X_0 + 1 X = np.stack((X_0, X_1)) y = (X_0 + X_1) / 3 - distances = distance_logeuclid_cpm(X, y, optimizer=optimizer) + distances = distance_logeuclid_cpm(X, y, optimizer=optimizer)[0] assert distances.argmin() == 0 From 22abf0941a8495995f3852cdfb72a3e79579d781 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Fri, 23 Feb 2024 18:39:21 +0100 Subject: [PATCH 67/90] fix lint --- pyriemann_qiskit/utils/distance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index b3ed406e..a3df8ec4 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -39,8 +39,8 @@ def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer()): provides the matrix of the convex hull closest to matrix B. distance : float Log-Euclidean distance between the SPD matrix B and the convex hull of the set - of SPD matrices A, defined as the distance between B and the matrix of the convex - hull closest to matrix B. + of SPD matrices A, defined as the distance between B and the matrix of + the convex hull closest to matrix B. Notes ----- From 725da6b052f92f4e9a2fb7e7b5fb740b8f49bc7a Mon Sep 17 00:00:00 2001 From: gcattan Date: Sat, 24 Feb 2024 21:58:54 +0100 Subject: [PATCH 68/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index a3df8ec4..3bbf31fb 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -82,12 +82,9 @@ def log_prod(m1, m2): result = optimizer.solve(prob, reshape=False) - # compute distance - _2VecLogYD = 2 * sum(result[i] * log_prod(B, A[i]) for i in matrices) - wtDw = sum( - result[i] * result[j] * log_prod(A[i], A[j]) for i in matrices for j in matrices - ) - distance = wtDw - _2VecLogYD + # compute nearest matrix and distance + C = mean_logeuclid(A, w) + distance = distance_logeuclid(C, B) return 1 - result, distance From 8e8eda331f5dbcdb388c67ee0c70429bfa6bc547 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Sat, 24 Feb 2024 22:00:43 +0100 Subject: [PATCH 69/90] add missing imports --- pyriemann_qiskit/utils/distance.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 3bbf31fb..0120ca08 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -4,6 +4,8 @@ from pyriemann.classification import MDM from pyriemann.utils.distance import distance_functions from pyriemann.utils.base import logm +from pyriemann.utils.distance import distance_logeuclid +from pyriemann.utils.mean import mean_logeuclid from typing_extensions import deprecated From 0f7fdd8a162740ffcaabcafa6b524867299e2c98 Mon Sep 17 00:00:00 2001 From: gcattan Date: Sat, 24 Feb 2024 22:05:58 +0100 Subject: [PATCH 70/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 0120ca08..63bc878e 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -2,9 +2,8 @@ from docplex.mp.model import Model from pyriemann_qiskit.utils.docplex import ClassicalOptimizer, get_global_optimizer from pyriemann.classification import MDM -from pyriemann.utils.distance import distance_functions +from pyriemann.utils.distance import distance_functions, distance_logeuclid from pyriemann.utils.base import logm -from pyriemann.utils.distance import distance_logeuclid from pyriemann.utils.mean import mean_logeuclid from typing_extensions import deprecated From 31fee4f3500a6b4a5f7230663f8bcbe8c61d3bf2 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Sat, 24 Feb 2024 23:49:13 +0100 Subject: [PATCH 71/90] fix test --- pyriemann_qiskit/utils/distance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 63bc878e..2a206598 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -84,7 +84,7 @@ def log_prod(m1, m2): result = optimizer.solve(prob, reshape=False) # compute nearest matrix and distance - C = mean_logeuclid(A, w) + C = mean_logeuclid(A, result) distance = distance_logeuclid(C, B) return 1 - result, distance From 0e3eca3fcaece80a51141ac4f2a929447fe6b730 Mon Sep 17 00:00:00 2001 From: gcattan Date: Mon, 26 Feb 2024 12:31:01 +0100 Subject: [PATCH 72/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 2a206598..6f6f5686 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -16,7 +16,7 @@ def logeucl_dist_convex(): pass -def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer()): +def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer(), return_weights=False): """Log-Euclidean distance to a convex hull of SPD matrices. Log-Euclidean distance between a SPD matrix B and the convex hull of a set From f8c3deb1f13c3ec043ac77c042a01139fc4cdad6 Mon Sep 17 00:00:00 2001 From: gcattan Date: Mon, 26 Feb 2024 12:31:11 +0100 Subject: [PATCH 73/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 6f6f5686..ae649f9c 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -81,7 +81,7 @@ def log_prod(m1, m2): prob.set_objective("min", objectives) - result = optimizer.solve(prob, reshape=False) + weights = optimizer.solve(prob, reshape=False) # compute nearest matrix and distance C = mean_logeuclid(A, result) From f27b79639f7dc66758bbf1943b81a2246c7b2c8d Mon Sep 17 00:00:00 2001 From: gcattan Date: Mon, 26 Feb 2024 12:32:46 +0100 Subject: [PATCH 74/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index ae649f9c..59982279 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -34,14 +34,14 @@ def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer(), return_weights= Returns ------- - weights : ndarray, shape (n_matrices,) - The optimized weights for the set of SPD matrices A. - Using these weights, the weighted Log-Euclidean mean of set A - provides the matrix of the convex hull closest to matrix B. distance : float Log-Euclidean distance between the SPD matrix B and the convex hull of the set of SPD matrices A, defined as the distance between B and the matrix of the convex hull closest to matrix B. + weights : ndarray, shape (n_matrices,) + If return_weights is True, it returns the optimized weights for the set of SPD matrices A. + Using these weights, the weighted Log-Euclidean mean of set A + provides the matrix of the convex hull closest to matrix B. Notes ----- From e4a5fccf91404474f26d00d40dedacd18f76ad94 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Mon, 26 Feb 2024 12:41:19 +0100 Subject: [PATCH 75/90] add constraint --- pyriemann_qiskit/utils/distance.py | 13 ++++++++++--- tests/test_utils_distance.py | 3 ++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 59982279..6cdc723f 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -81,13 +81,17 @@ def log_prod(m1, m2): prob.set_objective("min", objectives) + prob.add_constraint(prob.sum(w) == 1) + weights = optimizer.solve(prob, reshape=False) # compute nearest matrix and distance - C = mean_logeuclid(A, result) + C = mean_logeuclid(A, weights) distance = distance_logeuclid(C, B) - return 1 - result, distance + if return_weights: + return distance, weights + return distance _mdm_predict_distances_original = MDM._predict_distances @@ -96,7 +100,10 @@ def log_prod(m1, m2): def predict_distances(mdm, X): if mdm.metric_dist == "logeuclid_cpm": centroids = np.array(mdm.covmeans_) - return np.array([distance_logeuclid_cpm(centroids, x)[0] for x in X]) + def postprocessed_distances(x): + _, weights = distance_logeuclid_cpm(centroids, x, return_weights=True) + return 1 - weights + return np.array([postprocessed_distances(x) for x in X]) else: return _mdm_predict_distances_original(mdm, X) diff --git a/tests/test_utils_distance.py b/tests/test_utils_distance.py index e62a1a75..757781dc 100644 --- a/tests/test_utils_distance.py +++ b/tests/test_utils_distance.py @@ -28,5 +28,6 @@ def test_distance_logeuclid_cpm(optimizer): X_1 = X_0 + 1 X = np.stack((X_0, X_1)) y = (X_0 + X_1) / 3 - distances = distance_logeuclid_cpm(X, y, optimizer=optimizer)[0] + _, weights = distance_logeuclid_cpm(X, y, optimizer=optimizer, return_weights=True) + distances = 1 - weights assert distances.argmin() == 0 From 6cf239df8dd482f1fda51df318c5463feffbbe37 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 11:47:27 +0000 Subject: [PATCH 76/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- pyriemann_qiskit/utils/distance.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 6cdc723f..de92a253 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -100,9 +100,11 @@ def log_prod(m1, m2): def predict_distances(mdm, X): if mdm.metric_dist == "logeuclid_cpm": centroids = np.array(mdm.covmeans_) + def postprocessed_distances(x): _, weights = distance_logeuclid_cpm(centroids, x, return_weights=True) return 1 - weights + return np.array([postprocessed_distances(x) for x in X]) else: return _mdm_predict_distances_original(mdm, X) From f404dee0c98cd0623b46dca353c5dc2b37081d6a Mon Sep 17 00:00:00 2001 From: gcattan Date: Mon, 26 Feb 2024 13:35:30 +0100 Subject: [PATCH 77/90] Update pyriemann_qiskit/utils/distance.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Quentin Barthélemy --- pyriemann_qiskit/utils/distance.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index de92a253..c74c4659 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -31,6 +31,8 @@ def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer(), return_weights= SPD matrix. optimizer: pyQiskitOptimizer An instance of pyQiskitOptimizer. + return_weights : bool, default=False + Whether to return optimized weights. Returns ------- From 659217b051ff284de57195bf106403eac081c051 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Mon, 26 Feb 2024 13:53:38 +0100 Subject: [PATCH 78/90] add regularization based on GH console error --- tests/test_utils_distance.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_utils_distance.py b/tests/test_utils_distance.py index 757781dc..f52f130e 100644 --- a/tests/test_utils_distance.py +++ b/tests/test_utils_distance.py @@ -6,8 +6,8 @@ ) from pyriemann_qiskit.utils.distance import distance_logeuclid_cpm from pyriemann_qiskit.datasets import get_mne_sample -from pyriemann.classification import MDM -from pyriemann.estimation import XdawnCovariances +from pyriemann_qiskit.classification import QuanticMDM +from pyriemann.estimation import XdawnCovariances, Shrinkage from sklearn.pipeline import make_pipeline from sklearn.model_selection import StratifiedKFold, cross_val_score @@ -15,7 +15,7 @@ def test_performance(): metric = {"mean": "logeuclid", "distance": "logeuclid_cpm"} - clf = make_pipeline(XdawnCovariances(), MDM(metric=metric)) + clf = make_pipeline(XdawnCovariances(), QuanticMDM(metric=metric, regularization=Shrinkage(shrinkage=0.9), quantum=False)) skf = StratifiedKFold(n_splits=3) covset, labels = get_mne_sample() score = cross_val_score(clf, covset, labels, cv=skf, scoring="roc_auc") From 0fb54a7d437dac3fbc1fbbc6cdda0ca831b4ee98 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 12:54:08 +0000 Subject: [PATCH 79/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- tests/test_utils_distance.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_utils_distance.py b/tests/test_utils_distance.py index f52f130e..64fde32a 100644 --- a/tests/test_utils_distance.py +++ b/tests/test_utils_distance.py @@ -15,7 +15,12 @@ def test_performance(): metric = {"mean": "logeuclid", "distance": "logeuclid_cpm"} - clf = make_pipeline(XdawnCovariances(), QuanticMDM(metric=metric, regularization=Shrinkage(shrinkage=0.9), quantum=False)) + clf = make_pipeline( + XdawnCovariances(), + QuanticMDM( + metric=metric, regularization=Shrinkage(shrinkage=0.9), quantum=False + ), + ) skf = StratifiedKFold(n_splits=3) covset, labels = get_mne_sample() score = cross_val_score(clf, covset, labels, cv=skf, scoring="roc_auc") From 473d822978bee97fd8a7d090c57ab62a6c6ecc1f Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Mon, 26 Feb 2024 13:55:53 +0100 Subject: [PATCH 80/90] fix lint --- pyriemann_qiskit/utils/distance.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index c74c4659..34ccf94d 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -41,7 +41,8 @@ def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer(), return_weights= of SPD matrices A, defined as the distance between B and the matrix of the convex hull closest to matrix B. weights : ndarray, shape (n_matrices,) - If return_weights is True, it returns the optimized weights for the set of SPD matrices A. + If return_weights is True, + it returns the optimized weights for the set of SPD matrices A. Using these weights, the weighted Log-Euclidean mean of set A provides the matrix of the convex hull closest to matrix B. From 34a9267fe508fe83f34c98e2b69b82b099b6649c Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Mon, 26 Feb 2024 17:27:39 +0100 Subject: [PATCH 81/90] applu suggestion on method predict_distances --- pyriemann_qiskit/utils/distance.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 34ccf94d..664da102 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -104,11 +104,8 @@ def predict_distances(mdm, X): if mdm.metric_dist == "logeuclid_cpm": centroids = np.array(mdm.covmeans_) - def postprocessed_distances(x): - _, weights = distance_logeuclid_cpm(centroids, x, return_weights=True) - return 1 - weights - - return np.array([postprocessed_distances(x) for x in X]) + weights = [distance_logeuclid_cpm(centroids, x, return_weights=True)[1] for x in X] + return 1 - np.array(weights) else: return _mdm_predict_distances_original(mdm, X) From aebf84f29042bbdf1a3621fccba0dd26967dde5a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:28:20 +0000 Subject: [PATCH 82/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- pyriemann_qiskit/utils/distance.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 664da102..b2105c32 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -104,7 +104,9 @@ def predict_distances(mdm, X): if mdm.metric_dist == "logeuclid_cpm": centroids = np.array(mdm.covmeans_) - weights = [distance_logeuclid_cpm(centroids, x, return_weights=True)[1] for x in X] + weights = [ + distance_logeuclid_cpm(centroids, x, return_weights=True)[1] for x in X + ] return 1 - np.array(weights) else: return _mdm_predict_distances_original(mdm, X) From 0c6785d7761de882cbfce8c097cdc692dd5572ab Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Mon, 26 Feb 2024 17:31:15 +0100 Subject: [PATCH 83/90] add more regularization for GH CI --- tests/test_utils_distance.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_utils_distance.py b/tests/test_utils_distance.py index 64fde32a..3869d860 100644 --- a/tests/test_utils_distance.py +++ b/tests/test_utils_distance.py @@ -14,15 +14,17 @@ def test_performance(): metric = {"mean": "logeuclid", "distance": "logeuclid_cpm"} + regularization = Shrinkage(Shrinkage=0.9) clf = make_pipeline( XdawnCovariances(), QuanticMDM( - metric=metric, regularization=Shrinkage(shrinkage=0.9), quantum=False + metric=metric, regularization=regularization, quantum=False ), ) skf = StratifiedKFold(n_splits=3) covset, labels = get_mne_sample() + covset = regularization.fit_transform(covset) score = cross_val_score(clf, covset, labels, cv=skf, scoring="roc_auc") assert score.mean() > 0 From 18122ddcf5a23e78b4d4d0f9d007670870af65fe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:31:44 +0000 Subject: [PATCH 84/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- tests/test_utils_distance.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/test_utils_distance.py b/tests/test_utils_distance.py index 3869d860..9fee7639 100644 --- a/tests/test_utils_distance.py +++ b/tests/test_utils_distance.py @@ -18,9 +18,7 @@ def test_performance(): clf = make_pipeline( XdawnCovariances(), - QuanticMDM( - metric=metric, regularization=regularization, quantum=False - ), + QuanticMDM(metric=metric, regularization=regularization, quantum=False), ) skf = StratifiedKFold(n_splits=3) covset, labels = get_mne_sample() From 2eb9141db7b95eb2894b8e1de53defb355e1a6eb Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Mon, 26 Feb 2024 18:21:54 +0100 Subject: [PATCH 85/90] wrong file: revert changes to test_utils_distance, add regularization to test_utils_mean --- tests/test_utils_distance.py | 11 +++-------- tests/test_utils_mean.py | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/tests/test_utils_distance.py b/tests/test_utils_distance.py index 9fee7639..757781dc 100644 --- a/tests/test_utils_distance.py +++ b/tests/test_utils_distance.py @@ -6,23 +6,18 @@ ) from pyriemann_qiskit.utils.distance import distance_logeuclid_cpm from pyriemann_qiskit.datasets import get_mne_sample -from pyriemann_qiskit.classification import QuanticMDM -from pyriemann.estimation import XdawnCovariances, Shrinkage +from pyriemann.classification import MDM +from pyriemann.estimation import XdawnCovariances from sklearn.pipeline import make_pipeline from sklearn.model_selection import StratifiedKFold, cross_val_score def test_performance(): metric = {"mean": "logeuclid", "distance": "logeuclid_cpm"} - regularization = Shrinkage(Shrinkage=0.9) - clf = make_pipeline( - XdawnCovariances(), - QuanticMDM(metric=metric, regularization=regularization, quantum=False), - ) + clf = make_pipeline(XdawnCovariances(), MDM(metric=metric)) skf = StratifiedKFold(n_splits=3) covset, labels = get_mne_sample() - covset = regularization.fit_transform(covset) score = cross_val_score(clf, covset, labels, cv=skf, scoring="roc_auc") assert score.mean() > 0 diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index c0df1b12..3484d9d0 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -14,7 +14,7 @@ "kernel", [ ({"mean": "euclid_cpm", "distance": "euclid"}, Shrinkage(shrinkage=0.9)), - ({"mean": "euclid", "distance": "logeuclid_cpm"}, None), + ({"mean": "euclid", "distance": "logeuclid_cpm"}, Shrinkage(shrinkage=0.9)), ], ) def test_performance(get_covmats, get_labels, kernel): From 9f2be5d8c7af8ff0af98ab1fa756421a44051d58 Mon Sep 17 00:00:00 2001 From: qbarthelemy Date: Tue, 27 Feb 2024 09:16:14 +0100 Subject: [PATCH 86/90] add check_weights --- pyriemann_qiskit/utils/distance.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index b2105c32..8bdec925 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -5,6 +5,7 @@ from pyriemann.utils.distance import distance_functions, distance_logeuclid from pyriemann.utils.base import logm from pyriemann.utils.mean import mean_logeuclid +from pyriemann.utils.utils import check_weights from typing_extensions import deprecated @@ -37,9 +38,9 @@ def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer(), return_weights= Returns ------- distance : float - Log-Euclidean distance between the SPD matrix B and the convex hull of the set - of SPD matrices A, defined as the distance between B and the matrix of - the convex hull closest to matrix B. + Log-Euclidean distance between the SPD matrix B and the convex hull of + the set of SPD matrices A, defined as the distance between B and the + matrix of the convex hull closest to matrix B. weights : ndarray, shape (n_matrices,) If return_weights is True, it returns the optimized weights for the set of SPD matrices A. @@ -60,9 +61,6 @@ def distance_logeuclid_cpm(A, B, optimizer=ClassicalOptimizer(), return_weights= http://ibmdecisionoptimization.github.io/docplex-doc/cp/creating_model.html """ - - optimizer = get_global_optimizer(optimizer) - n_matrices, _, _ = A.shape matrices = range(n_matrices) @@ -70,24 +68,22 @@ def log_prod(m1, m2): return np.nansum(logm(m1).flatten() * logm(m2).flatten()) prob = Model() - + optimizer = get_global_optimizer(optimizer) # should be part of the optimizer w = optimizer.get_weights(prob, matrices) - _2VecLogYD = 2 * prob.sum(w[i] * log_prod(B, A[i]) for i in matrices) - - wtDw = prob.sum( + wtLogAtLogAw = prob.sum( w[i] * w[j] * log_prod(A[i], A[j]) for i in matrices for j in matrices ) - - objectives = wtDw - _2VecLogYD + wLogBLogA = prob.sum(w[i] * log_prod(B, A[i]) for i in matrices) + objectives = wtLogAtLogAw - 2 * wLogBLogA prob.set_objective("min", objectives) - prob.add_constraint(prob.sum(w) == 1) - weights = optimizer.solve(prob, reshape=False) + weights = check_weights(weights, n_matrices, check_positivity=True) + # compute nearest matrix and distance C = mean_logeuclid(A, weights) distance = distance_logeuclid(C, B) From dccfd51f189e6129fe80361634267b9ae17d7fef Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Tue, 27 Feb 2024 11:26:22 +0100 Subject: [PATCH 87/90] replace make_covariances by make_matrices in conftest.py --- tests/conftest.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 333f39a2..fcb928a9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ import pytest import numpy as np from functools import partial -from pyriemann.datasets import make_covariances +from pyriemann.datasets import make_matrices from pyriemann_qiskit.datasets import get_mne_sample from operator import itemgetter @@ -36,7 +36,7 @@ def rndstate(): @pytest.fixture def get_covmats(rndstate): def _gen_cov(n_matrices, n_channels): - return make_covariances(n_matrices, n_channels, rndstate, return_params=False) + return make_matrices(n_matrices, n_channels, "spd", rndstate, return_params=False) return _gen_cov @@ -44,7 +44,7 @@ def _gen_cov(n_matrices, n_channels): @pytest.fixture def get_covmats_params(rndstate): def _gen_cov_params(n_matrices, n_channels): - return make_covariances(n_matrices, n_channels, rndstate, return_params=True) + return make_matrices(n_matrices, n_channels, "spd", rndstate, return_params=True) return _gen_cov_params @@ -94,11 +94,11 @@ def _get_dataset(n_samples, n_features, n_classes, type="bin"): samples = _get_separable_feats(n_samples, n_features, n_classes) labels = _get_labels(n_samples, n_classes) elif type == "rand_cov": - samples = make_covariances(n_samples, n_features, 0, return_params=False) + samples = make_matrices(n_samples, n_features, "spd", 0, return_params=False) labels = _get_labels(n_samples, n_classes) elif type == "bin_cov": - samples_0 = make_covariances( - n_samples // n_classes, n_features, 0, return_params=False + samples_0 = make_matrices( + n_samples // n_classes, n_features, "spd", 0, return_params=False ) samples = np.concatenate( [samples_0 * (i + 1) for i in range(n_classes)], axis=0 From b2753cf910d4ef4ebfddd63d1008def6c22d1308 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:26:39 +0000 Subject: [PATCH 88/90] [pre-commit.ci] auto fixes from pre-commit.com hooks --- tests/conftest.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index fcb928a9..ea97f52b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -36,7 +36,9 @@ def rndstate(): @pytest.fixture def get_covmats(rndstate): def _gen_cov(n_matrices, n_channels): - return make_matrices(n_matrices, n_channels, "spd", rndstate, return_params=False) + return make_matrices( + n_matrices, n_channels, "spd", rndstate, return_params=False + ) return _gen_cov @@ -44,7 +46,9 @@ def _gen_cov(n_matrices, n_channels): @pytest.fixture def get_covmats_params(rndstate): def _gen_cov_params(n_matrices, n_channels): - return make_matrices(n_matrices, n_channels, "spd", rndstate, return_params=True) + return make_matrices( + n_matrices, n_channels, "spd", rndstate, return_params=True + ) return _gen_cov_params @@ -94,7 +98,9 @@ def _get_dataset(n_samples, n_features, n_classes, type="bin"): samples = _get_separable_feats(n_samples, n_features, n_classes) labels = _get_labels(n_samples, n_classes) elif type == "rand_cov": - samples = make_matrices(n_samples, n_features, "spd", 0, return_params=False) + samples = make_matrices( + n_samples, n_features, "spd", 0, return_params=False + ) labels = _get_labels(n_samples, n_classes) elif type == "bin_cov": samples_0 = make_matrices( From f4bf15719682df63daa08dbd035a4a4f3e2f868c Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Tue, 27 Feb 2024 12:22:19 +0100 Subject: [PATCH 89/90] fix test by using mne data add test for logeuclid_mean --- tests/test_utils_mean.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_utils_mean.py b/tests/test_utils_mean.py index 3484d9d0..2854ad5d 100644 --- a/tests/test_utils_mean.py +++ b/tests/test_utils_mean.py @@ -7,6 +7,7 @@ from pyriemann_qiskit.utils.mean import mean_euclid_cpm, mean_logeuclid_cpm from pyriemann_qiskit.utils import ClassicalOptimizer, NaiveQAOAOptimizer from pyriemann_qiskit.classification import QuanticMDM +from pyriemann_qiskit.datasets import get_mne_sample from qiskit_optimization.algorithms import ADMMOptimizer @@ -14,19 +15,18 @@ "kernel", [ ({"mean": "euclid_cpm", "distance": "euclid"}, Shrinkage(shrinkage=0.9)), - ({"mean": "euclid", "distance": "logeuclid_cpm"}, Shrinkage(shrinkage=0.9)), + ({"mean": "logeuclid_cpm", "distance": "logeuclid"}, Shrinkage(shrinkage=0.9)), ], ) -def test_performance(get_covmats, get_labels, kernel): +def test_performance(kernel): metric, regularization = kernel clf = make_pipeline( XdawnCovariances(), QuanticMDM(metric=metric, regularization=regularization, quantum=False), ) - skf = StratifiedKFold(n_splits=5) - n_matrices, n_channels, n_classes = 100, 3, 2 - covset = get_covmats(n_matrices, n_channels) - labels = get_labels(n_matrices, n_classes) + skf = StratifiedKFold(n_splits=3) + + covset, labels = get_mne_sample() score = cross_val_score(clf, covset, labels, cv=skf, scoring="roc_auc") assert score.mean() > 0 From f6a432d065934ff3fe21b06e36fad2e8e2f46e25 Mon Sep 17 00:00:00 2001 From: Gregoire Cattan Date: Tue, 27 Feb 2024 15:00:55 +0100 Subject: [PATCH 90/90] remove line with check_weights --- pyriemann_qiskit/utils/distance.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyriemann_qiskit/utils/distance.py b/pyriemann_qiskit/utils/distance.py index 8bdec925..eb2b8df9 100644 --- a/pyriemann_qiskit/utils/distance.py +++ b/pyriemann_qiskit/utils/distance.py @@ -5,7 +5,6 @@ from pyriemann.utils.distance import distance_functions, distance_logeuclid from pyriemann.utils.base import logm from pyriemann.utils.mean import mean_logeuclid -from pyriemann.utils.utils import check_weights from typing_extensions import deprecated @@ -82,8 +81,6 @@ def log_prod(m1, m2): prob.add_constraint(prob.sum(w) == 1) weights = optimizer.solve(prob, reshape=False) - weights = check_weights(weights, n_matrices, check_positivity=True) - # compute nearest matrix and distance C = mean_logeuclid(A, weights) distance = distance_logeuclid(C, B)