diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..7ff1ba24 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,8 @@ +[report] +include = */pyriemann/* +omit = + */python?.?/* + */site-packages/pytest/* + */setup.py + */tests/* + */examples/* diff --git a/.github/workflows/deploy_ghpages.yml b/.github/workflows/deploy_ghpages.yml new file mode 100644 index 00000000..daa8ef63 --- /dev/null +++ b/.github/workflows/deploy_ghpages.yml @@ -0,0 +1,52 @@ +name: Deploy GitHub pages + +on: [push, pull_request] + + +jobs: + build_docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Generate HTML docs + uses: ammaraskar/sphinx-action@master + with: + docs-folder: "doc/" + pre-build-command: | + apt-get update + pip install -e . + pip install -r doc/doc-requirements.txt + - name: Upload generated HTML as artifact + uses: actions/upload-artifact@v2 + with: + name: DocHTML + path: doc/build/html/ + + # deploy_docs: + # if: github.ref == 'refs/heads/master' + # needs: + # build_docs + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v2 + # - name: Download artifacts + # uses: actions/download-artifact@v2 + # with: + # name: DocHTML + # path: doc/build/html/ + # - name: Commit to documentation branch + # run: | + # git clone --no-checkout --depth 1 https://github.com/${{ github.repository_owner }}/qndiag.git --branch gh-pages --single-branch gh-pages + # cp -r doc/build/html/* gh-pages/ + # cd gh-pages + # touch .nojekyll + # git config --local user.email "pyriemann@github.com" + # git config --local user.name "pyriemann GitHub Action" + # git add . + # git commit -m "Update documentation" -a || true + # - name: Push changes + # uses: ad-m/github-push-action@v0.6.0 + # with: + # branch: gh-pages + # directory: gh-pages + # github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml new file mode 100644 index 00000000..c700d96b --- /dev/null +++ b/.github/workflows/testing.yml @@ -0,0 +1,39 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: testing + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + python-version: [3.6, 3.7, 3.8] + os: [ubuntu-latest, macOS-latest, windows-latest] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + - name: Install package + run: | + python -m pip install .[tests] + - name: Lint with flake8 + run: | + flake8 examples tests pyriemann + - name: Test with pytest + run: | + pytest diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..1e7be755 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.pyc +.DS_Store +.coverage +.coveralls.yml +/doc/build +/doc/generated +/doc/auto_examples +/dist +/pyriemann_qiskit.egg-info/* +*-e diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..9295ecab --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,13 @@ +default_language_version: + python: python3.7 +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.4.0 + hooks: + - id: check-yaml + - id: check-json + - id: check-added-large-files + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-case-conflict + - id: mixed-line-ending diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d399506e --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright © 2015, authors of pyRiemann +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the names of pyriemann authors nor the names of any + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..bb3ec5f0 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include README.md diff --git a/README.md b/README.md index 7e8c6cc6..9b2374d6 100644 --- a/README.md +++ b/README.md @@ -1 +1,64 @@ -# pyRiemann-qiskit \ No newline at end of file +# pyRiemann-qiskit + +Litterature on quantum computing suggests it may offer an advantage as compared +with classical computing in terms of computational time and outcomes, such as +for pattern recognition or when using limited training sets [1, 2]. + +A ubiquitous library on quantum computing is Qiskit [3]. +Qiskit is an IBM library distributed under Apache 2.0 which provides both +quantum algorithms and backends. A backend can be either your local machine +or a remote machine, which one can emulates or be a quantum machine. +Qiskit abstraction over the type of machine you want to use, make designing +quantum algorithm seamless. + +Qiskit implements a quantum version of support vector +-like classifiers, known as quantum-enhanced support vector classifier (QSVC) +and varitional quantum classifier (VQC) [4]. These classifiers likely offer +an advantage over classical SVM in situations where the classification task +is complex. Task complexity is raised by the encoding of the data into a +quantum state, the number of available data and the quality of the data. +Although there is no study on this topic at the time of writting, +this could be an interesting research direction to investigate illiteracy +in the domain of brain computer interfaces. + +pyRiemann-qiskit implements a wrapper around QSVC and VQC, to use quantum +classification with riemanian geometry. A use case would be to use vectorized +covariance matrices in the TangentSpace as an input for these classifiers, +enabling a possible sandbox for researchers and engineers in the field. + +## References + +[1] A. Blance and M. Spannowsky, + ‘Quantum machine learning for particle physics using a variational quantum classifier’, + J. High Energ. Phys., vol. 2021, no. 2, p. 212, Feb. 2021, + doi: 10.1007/JHEP02(2021)212. + +[2] P. Rebentrost, M. Mohseni, and S. Lloyd, + ‘Quantum Support Vector Machine for Big Data Classification’, + Phys. Rev. Lett., vol. 113, no. 13, p. 130503, Sep. 2014, + doi: 10.1103/PhysRevLett.113.130503. + +[3] H. Abraham et al., Qiskit: An Open-source Framework for Quantum Computing. + Zenodo, 2019. doi: 10.5281/zenodo.2562110. + +[4] V. Havlíček et al., + ‘Supervised learning with quantum-enhanced feature spaces’, + Nature, vol. 567, no. 7747, pp. 209–212, Mar. 2019, + doi: 10.1038/s41586-019-0980-2. + +## Installation + +As there is no stable version, you should clone this repository +and install the package on your local machine using the `setup.py` script + +``` +python setup.py develop +``` + +To check the installation, open a python shell and type: + +``` +import pyriemann_qiskit +``` + +Full documentation, including API description, is available at \ No newline at end of file diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 00000000..cac96cf5 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + yes | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyRiemann.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyRiemann.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/pyRiemann" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyRiemann" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/doc/_static/copybutton.js b/doc/_static/copybutton.js new file mode 100644 index 00000000..0a7db6d6 --- /dev/null +++ b/doc/_static/copybutton.js @@ -0,0 +1,59 @@ +// originally taken from scikit-learn's Sphinx theme +$(document).ready(function() { + /* Add a [>>>] button on the top-right corner of code samples to hide + * the >>> and ... prompts and the output and thus make the code + * copyable. + * Note: This JS snippet was taken from the official python.org + * documentation site.*/ + var div = $('.highlight-python .highlight,' + + '.highlight-python3 .highlight,' + + '.highlight-pycon .highlight') + var pre = div.find('pre'); + + // get the styles from the current theme + pre.parent().parent().css('position', 'relative'); + var hide_text = 'Hide the prompts and output'; + var show_text = 'Show the prompts and output'; + var border_width = pre.css('border-top-width'); + var border_style = pre.css('border-top-style'); + var border_color = pre.css('border-top-color'); + var button_styles = { + 'cursor':'pointer', 'position': 'absolute', 'top': '0', 'right': '0', + 'border-color': border_color, 'border-style': border_style, + 'border-width': border_width, 'color': border_color, 'text-size': '75%', + 'font-family': 'monospace', 'padding-left': '0.2em', 'padding-right': '0.2em' + } + + // create and add the button to all the code blocks that contain >>> + div.each(function(index) { + var jthis = $(this); + if (jthis.find('.gp').length > 0) { + var button = $('>>>'); + button.css(button_styles) + button.attr('title', hide_text); + jthis.prepend(button); + } + // tracebacks (.gt) contain bare text elements that need to be + // wrapped in a span to work with .nextUntil() (see later) + jthis.find('pre:has(.gt)').contents().filter(function() { + return ((this.nodeType == 3) && (this.data.trim().length > 0)); + }).wrap(''); + }); + + // define the behavior of the button when it's clicked + $('.copybutton').toggle( + function() { + var button = $(this); + button.parent().find('.go, .gp, .gt').hide(); + button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'hidden'); + button.css('text-decoration', 'line-through'); + button.attr('title', show_text); + }, + function() { + var button = $(this); + button.parent().find('.go, .gp, .gt').show(); + button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'visible'); + button.css('text-decoration', 'none'); + button.attr('title', hide_text); + }); +}); diff --git a/doc/_static/style.css b/doc/_static/style.css new file mode 100644 index 00000000..74adc0f5 --- /dev/null +++ b/doc/_static/style.css @@ -0,0 +1,117 @@ +body { color: #444444 !important; } + +h1 { font-size: 40px !important; } +h2 { font-size: 32px !important; } +h3 { font-size: 24px !important; } +h4 { font-size: 18px !important; } +h5 { font-size: 14px !important; } +h6 { font-size: 10px !important; } + +footer a{ + + color: #4c72b0 !important; +} +a.reference { + color: #4c72b0 !important; +} + +blockquote p { + font-size: 14px !important; +} + +blockquote { + padding-top: 4px !important; + padding-bottom: 4px !important; + margin: 0 0 0px !important; +} + +pre { + background-color: #f6f6f9 !important; +} + +code { + color: #49759c !important; + background-color: transparent; !important; +} + +code.descclassname { + padding-right: 0px !important; +} + +code.descname { + padding-left: 0px !important; +} + +dt:target, span.highlighted { + background-color: #ffffff !important; +} + +ul { + padding-left: 20px !important; +} + +ul.dropdown-menu { + padding-left: 0px !important; +} + +.alert-info { + background-color: #adb8cb !important; + border-color: #adb8cb !important; + color: #2c3e50 !important; +} + +/* From https://github.com/twbs/bootstrap/issues/1768 */ +*[id]:before { + /* display: block; */ + content: " "; + margin-top: -60px; + height: 60px; + visibility: hidden; +} + +.dataframe table { + /*Uncomment to center tables horizontally*/ + /* margin-left: auto; */ + /* margin-right: auto; */ + border: none; + border-collapse: collapse; + border-spacing: 0; + font-size: 12px; + table-layout: fixed; +} + +.dataframe thead { + border-bottom: 1px solid; + vertical-align: bottom; +} + +.dataframe tr, th, td { + text-align: left; + vertical-align: middle; + padding: 0.5em 0.5em; + line-height: normal; + white-space: normal; + max-width: none; + border: none; +} + +.dataframe th { + font-weight: bold; +} + +table { + margin-bottom: 20px; +} + +tbody tr:nth-child(odd) { + background: #f5f5f5; +} + +tbody tr:hover { + background: rgba(66, 165, 245, 0.2); +} + +div.body { + min-width: None; + max-width: None; +} diff --git a/doc/_templates/class.rst b/doc/_templates/class.rst new file mode 100644 index 00000000..f71c4d49 --- /dev/null +++ b/doc/_templates/class.rst @@ -0,0 +1,10 @@ +{{ fullname }} +{{ underline }} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + + {% block methods %} + .. automethod:: __init__ + {% endblock %} diff --git a/doc/_templates/function.rst b/doc/_templates/function.rst new file mode 100644 index 00000000..4d7ea38a --- /dev/null +++ b/doc/_templates/function.rst @@ -0,0 +1,6 @@ +{{ fullname }} +{{ underline }} + +.. currentmodule:: {{ module }} + +.. autofunction:: {{ objname }} diff --git a/doc/api.rst b/doc/api.rst new file mode 100644 index 00000000..116d4413 --- /dev/null +++ b/doc/api.rst @@ -0,0 +1,5 @@ +.. _api_ref: + +============= +API reference +============= \ No newline at end of file diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 00000000..21ddff83 --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,335 @@ +# -*- coding: utf-8 -*- +# +# pyRiemann-qiskit documentation build configuration file, created by +# sphinx-quickstart on Sun Apr 19 13:17:55 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +import matplotlib + +# mne update path +import mne +print(mne.datasets.sample.data_path(update_path=True)) +print(mne.datasets.eegbci.load_data(1, [6, 10, 14], update_path=True)) + +matplotlib.use('Agg') +import shlex +import sphinx_gallery +import sphinx_bootstrap_theme + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.coverage', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + # 'sphinx.ext.linkcode', + # 'sphinx.ext.mathjax', + 'sphinx.ext.imgmath', + 'sphinx.ext.todo', + 'numpydoc', + # 'sphinx.ext.ifconfig', + # 'sphinx.ext.viewcode', + 'sphinx_gallery.gen_gallery', +] +plot_include_source = True +plot_formats = [("png", 90)] +plot_html_show_formats = False +plot_html_show_source_link = False + +sphinx_gallery_conf = { + 'examples_dirs': ['../examples', '../tutorials'], + 'gallery_dirs': ['auto_examples'] +} +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] +autodoc_default_flags = ['inherited-members'] + +autosummary_generate = True +numpydoc_show_class_members = False + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'pyRiemann-qiskit' +copyright = u'2015-2021, PyRiemann Contributors' +author = u'Gregoire Cattan' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +sys.path.insert(0, os.path.abspath(os.path.pardir)) +import pyriemann_qiskit +version = pyriemann_qiskit.__version__ +# The full version, including alpha/beta/rc tags. +release = pyriemann_qiskit.__version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'bootstrap' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +html_theme_options = { + 'source_link_position': + "footer", + 'bootswatch_theme': + "flatly", + 'navbar_sidebarrel': + False, + 'bootstrap_version': + "3", + 'navbar_links': [("API", "api"), ("Gallery", "auto_examples/index")], +} + +# Add any paths that contain custom themes here, relative to this directory. +html_theme_path = sphinx_bootstrap_theme.get_html_theme_path() + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Language to be used for generating the HTML full-text search index. +# Sphinx supports the following languages: +# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' +# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' +#html_search_language = 'en' + +# A dictionary with options for the search language support, empty by default. +# Now only 'ja' uses this config value +#html_search_options = {'type': 'default'} + +# The name of a javascript file (relative to the configuration directory) that +# implements a search results scorer. If empty, the default will be used. +#html_search_scorer = 'scorer.js' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'pyriemann_qiskit_doc' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + #'preamble': '', + + # Latex figure (float) alignment + #'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'pyriemann_qiskit.tex', u'pyriemann-qiskit Documentation', + u'Gregoire Cattan', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, 'pyriemann-qiskit', u'pyRiemann-qiskit Documentation', [author], + 1)] + +# If true, show URL addresses after external links. +#man_show_urls = False + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'pyriemann-qiskit', u'pyRiemann-qiskit Documentation', author, 'pyriemann-qiskit', + 'Qiskit wrapper for pyRiemann', 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/': None} + + +def setup(app): + app.add_javascript('copybutton.js') + app.add_stylesheet('style.css') diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 00000000..8000a411 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,75 @@ +.. raw:: html + + + +pyRiemann-qiskit: Qiskit wrapper for pyRiemann +============================================================= + +pyRiemann-qiskit is a Qiskit wrapper around pyRiemann. +It allows to use quantum classification with Riemannian geometry. +pyRiemann-qiskit provides through Qiskit: +- a sandbox to experiments quantum computing; +- a way to leverage situations where classical computing failed + or terminates in exponential times; +- an abstraction layer on the quantum backend: + run your algorithm on either your local or a real quantum machine; +- a way to encode EEG/MEG data into a quantum state; +- a way to measure quantum bit into classical bits. + + +A typical use case would be to use vectorized covariance matrices in +TangentSpace as an input for quantum classifiers. + +For a brief introduction to the ideas behind the package, you can read the +:ref:`introductory notes `. More practical information is on the +:ref:`installation page `. You may also want to browse the +`example gallery `_ to get a sense for what you can do with pyRiemann-qiskit +and :ref:`API reference ` to find out how. + +To see the code or report a bug, please visit the `github repository +`_. + +.. raw:: html + + +
+
+
+

Content

+
+
+ +.. toctree:: + :maxdepth: 1 + + Introduction + Release notes + Installing + Example gallery + API reference + +.. raw:: html + +
+
+
+ + + diff --git a/doc/installing.rst b/doc/installing.rst new file mode 100644 index 00000000..3a915900 --- /dev/null +++ b/doc/installing.rst @@ -0,0 +1,46 @@ +.. _installing: + +Installing pyRiemann-qiskit +=========================== + +There is no yet stable version of pyRiemann-qiskit. + +Therefore, it is recommanded to clone the source code on `github `__ and install directly the package from source. + +``pip install -e .`` + +The install script will install the required dependencies. If you want also to build the documentation and to run the test locally, you could install all development dependencies with + +``pip install -e .[docs,tests]`` + +If you use a zsh shell, you need to write `pip install -e .\[docs,tests\]`. If you do not know what zsh is, you could use the above command. + +You may check that the package was correctly installed by starting a python shell and writing: + +``import pyriemann_qiskit`` + +Dependencies +~~~~~~~~~~~~ + +- Python (>= 3.6) + +Mandatory dependencies +^^^^^^^^^^^^^^^^^^^^^^ + +- `cython `__ + +- `pyriemann `__ + +- `qiskit==0.20.0 `__ + +- `cvxpy=1.1.12 `__ + +Recommended dependencies +^^^^^^^^^^^^^^^^^^^^^^^^ +These dependencies are recommanded to use the plotting functions of pyriemann or to run examples and tutorials, but they are not mandatory: + +- `matplotlib>=2.2 `__ + +- `mne-python `__ + +- `seaborn `__ diff --git a/doc/introduction.rst b/doc/introduction.rst new file mode 100644 index 00000000..386bb1ad --- /dev/null +++ b/doc/introduction.rst @@ -0,0 +1,48 @@ +.. _introduction: + +Introduction to pyRiemann-qiskit +================================ + +Litterature on quantum computing suggests it may offer an advantage as compared +with classical computing in terms of computational time and outcomes, such as +for pattern recognition or when using limited training sets [1, 2]. + +A ubiquitous library on quantum computing is Qiskit [3]. +Qiskit is an IBM library distributed under Apache 2.0 which provides both +quantum algorithms and backends. A backend can be either your local machine +or a remote machine, which one can emulates or be a quantum machine. +Qiskit abstraction over the type of machine you want to use, make designing +quantum algorithm seamless. + +Qiskit implements a quantum version of support vector +-like classifiers, known as quantum-enhanced support vector classifier (QSVC) +and varitional quantum classifier (VQC) [4]. These classifiers likely offer +an advantage over classical SVM in situations where the classification task +is complex. Task complexity is raised by the encoding of the data into a +quantum state, the number of available data and the quality of the data. +Although there is no study on this topic at the time of writting, +this could be an interesting research direction to investigate illiteracy +in the domain of brain computer interfaces. + +pyRiemann-qiskit implements a wrapper around QSVC and VQC, to use quantum +classification with riemanian geometry. A use case would be to use vectorized +covariance matrices in the TangentSpace as an input for these classifiers, +enabling a possible sandbox for researchers and engineers in the field. + +[1] A. Blance and M. Spannowsky, + ‘Quantum machine learning for particle physics using a variational quantum classifier’, + J. High Energ. Phys., vol. 2021, no. 2, p. 212, Feb. 2021, + doi: 10.1007/JHEP02(2021)212. + +[2] P. Rebentrost, M. Mohseni, and S. Lloyd, + ‘Quantum Support Vector Machine for Big Data Classification’, + Phys. Rev. Lett., vol. 113, no. 13, p. 130503, Sep. 2014, + doi: 10.1103/PhysRevLett.113.130503. + +[3] H. Abraham et al., Qiskit: An Open-source Framework for Quantum Computing. + Zenodo, 2019. doi: 10.5281/zenodo.2562110. + +[4] V. Havlíček et al., + ‘Supervised learning with quantum-enhanced feature spaces’, + Nature, vol. 567, no. 7747, pp. 209–212, Mar. 2019, + doi: 10.1038/s41586-019-0980-2. diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 00000000..7b11425e --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,11 @@ +sphinx-gallery +sphinx-bootstrap_theme +numpydoc +cython +mne +seaborn +scikit-learn +joblib +pandas +qiskit +pyriemann diff --git a/doc/whatsnew.rst b/doc/whatsnew.rst new file mode 100644 index 00000000..99d1f856 --- /dev/null +++ b/doc/whatsnew.rst @@ -0,0 +1,11 @@ +.. _whatsnew: + +.. currentmodule:: pyriemann-qiskit + +What's new in the package +========================= + +A catalog of new features, improvements, and bug-fixes in each release. + +v0.0.1.dev +---------- diff --git a/examples/README.txt b/examples/README.txt new file mode 100644 index 00000000..0db9ef0e --- /dev/null +++ b/examples/README.txt @@ -0,0 +1,6 @@ +Examples Gallery +================ + +.. contents:: Contents + :local: + :depth: 2 diff --git a/pyriemann_qiskit/__init__.py b/pyriemann_qiskit/__init__.py new file mode 100644 index 00000000..84fedf2e --- /dev/null +++ b/pyriemann_qiskit/__init__.py @@ -0,0 +1,5 @@ +from ._version import __version__ + +__all__ = [ + '__version__', +] diff --git a/pyriemann_qiskit/_version.py b/pyriemann_qiskit/_version.py new file mode 100644 index 00000000..511a3465 --- /dev/null +++ b/pyriemann_qiskit/_version.py @@ -0,0 +1 @@ +__version__ = '0.0.1.dev' diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..c6b42198 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +cython +pyriemann +qiskit==0.20.0 +cvxpy==1.1.12 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..3f7d987b --- /dev/null +++ b/setup.cfg @@ -0,0 +1,13 @@ +[bdist_wheel] +# This flag says that the code is written to work on both Python 2 and Python +# 3. If at all possible, it is good practice to do this. If you cannot, you +# will need to generate wheels for each Python version that you support. +universal=1 + +[build_sphinx] +source-dir = doc/ +build-dir = doc/build +all_files = 1 + +[upload_sphinx] +upload-dir = doc/build/html diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..72d37791 --- /dev/null +++ b/setup.py @@ -0,0 +1,40 @@ +import os.path as op + +from setuptools import setup, find_packages + + +# get the version (don't import mne here, so dependencies are not needed) +version = None +with open(op.join('pyriemann_qiskit', '_version.py'), 'r') as fid: + for line in (line.strip() for line in fid): + if line.startswith('__version__'): + version = line.split('=')[1].strip().strip('\'') + break +if version is None: + raise RuntimeError('Could not determine version') + +with open('README.md', 'r', encoding="utf8") as fid: + long_description = fid.read() + +setup(name='pyriemann-qiskit', + version=version, + description='Qiskit wrapper for pyRiemann', + url='https://pyriemann-qiskit.readthedocs.io', + author='Gregoire Cattan', + author_email='gcattan@hotmail.com', + license='BSD (3-clause)', + packages=find_packages(), + long_description=long_description, + long_description_content_type='text/markdown', + project_urls={ + 'Documentation': 'https://pyriemann.readthedocs.io', + 'Source': 'https://github.com/pyRiemann/pyRiemann-qiskit', + 'Tracker': 'https://github.com/pyRiemann/pyRiemann-qiskit/issues/', + }, + platforms='any', + python_requires=">=3.6", + install_requires=['cython', 'pyriemann', 'qiskit==0.20.0', 'cvxpy==1.1.12'], + extras_require={'docs': ['sphinx-gallery', 'sphinx-bootstrap_theme', 'numpydoc', 'mne', 'seaborn'], + 'tests': ['pytest', 'seaborn', 'flake8']}, + zip_safe=False, +) diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..7ecb2226 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,200 @@ +import pytest +from pytest import approx +import numpy as np +from functools import partial + + +def requires_module(function, name, call=None): + """Skip a test if package is not available (decorator).""" + call = ("import %s" % name) if call is None else call + reason = "Test %s skipped, requires %s." % (function.__name__, name) + try: + exec(call) in globals(), locals() + except Exception as exc: + if len(str(exc)) > 0 and str(exc) != "No module named %s" % name: + reason += " Got exception (%s)" % (exc,) + skip = True + else: + skip = False + return pytest.mark.skipif(skip, reason=reason)(function) + + +requires_matplotlib = partial(requires_module, name="matplotlib") +requires_seaborn = partial(requires_module, name="seaborn") + + +def generate_cov(n_trials, n_channels, rs, return_params=False): + """Generate a set of covariances matrices for test purpose""" + diags = 2.0 + 0.1 * rs.randn(n_trials, n_channels) + A = 2 * rs.rand(n_channels, n_channels) - 1 + A /= np.linalg.norm(A, axis=1)[:, np.newaxis] + covmats = np.empty((n_trials, n_channels, n_channels)) + for i in range(n_trials): + covmats[i] = A @ np.diag(diags[i]) @ A.T + if return_params: + return covmats, diags, A + else: + return covmats + + +@pytest.fixture +def rndstate(): + return np.random.RandomState(1234) + + +@pytest.fixture +def get_covmats(rndstate): + def _gen_cov(n_trials, n_chan): + return generate_cov(n_trials, n_chan, rndstate, return_params=False) + + return _gen_cov + + +@pytest.fixture +def get_covmats_params(rndstate): + def _gen_cov_params(n_trials, n_chan): + return generate_cov(n_trials, n_chan, rndstate, return_params=True) + + return _gen_cov_params + + +@pytest.fixture +def get_labels(): + def _get_labels(n_trials, n_classes): + return np.arange(n_classes).repeat(n_trials // n_classes) + + return _get_labels + + +def is_positive_semi_definite(X): + """Check if all matrices are positive semi-definite. + + Parameters + ---------- + X : ndarray, shape (..., n, n) + The set of square matrices, at least 2D ndarray. + + Returns + ------- + ret : boolean + True if all matrices are positive semi-definite. + """ + cs = X.shape[-1] + return np.all(np.linalg.eigvals(X.reshape((-1, cs, cs))) >= 0.0) + + +def is_positive_definite(X): + """Check if all matrices are positive definite. + + Parameters + ---------- + X : ndarray, shape (..., n, n) + The set of square matrices, at least 2D ndarray. + + Returns + ------- + ret : boolean + True if all matrices are positive definite. + """ + cs = X.shape[-1] + return np.all(np.linalg.eigvals(X.reshape((-1, cs, cs))) > 0.0) + + +def is_symmetric(X): + """Check if all matrices are symmetric. + + Parameters + ---------- + X : ndarray, shape (..., n, n) + The set of square matrices, at least 2D ndarray. + + Returns + ------- + ret : boolean + True if all matrices are symmetric. + """ + return X == approx(np.swapaxes(X, -2, -1)) + + +@pytest.fixture +def is_spd(): + """Check if all matrices are symmetric positive-definite. + + Parameters + ---------- + X : ndarray, shape (..., n, n) + The set of square matrices, at least 2D ndarray. + + Returns + ------- + ret : boolean + True if all matrices are symmetric positive-definite. + """ + + def _is_spd(X): + return is_symmetric(X) and is_positive_definite(X) + + return _is_spd + + +@pytest.fixture +def is_spsd(): + """Check if all matrices are symmetric positive semi-definite. + + Parameters + ---------- + X : ndarray, shape (..., n, n) + The set of square matrices, at least 2D ndarray. + + Returns + ------- + ret : boolean + True if all matrices are symmetric positive semi-definite. + """ + + def _is_spsd(X): + return is_symmetric(X) and is_positive_semi_definite(X) + + return _is_spsd + + +def get_distances(): + distances = [ + "riemann", + "logeuclid", + "euclid", + "logdet", + "kullback", + "kullback_right", + "kullback_sym", + ] + for dist in distances: + yield dist + + +def get_means(): + means = [ + "riemann", + "logeuclid", + "euclid", + "logdet", + "identity", + "wasserstein", + "ale", + "harmonic", + "kullback_sym", + ] + for mean in means: + yield mean + + +def get_metrics(): + metrics = [ + "riemann", + "logeuclid", + "euclid", + "logdet", + "kullback_sym", + ] + for met in metrics: + yield met