diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..8b690526 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +SPHINX_GITHUB_CHANGELOG_TOKEN=... \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..0552e8de --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +build/ +docs/html/ +out/ +*.egg-info/ +dist/ +__pycache__/ +*.pyc +.vscode/ +.mypy_cache/ +.pytest_cache/ +.dockerenv +.env \ No newline at end of file diff --git a/README.md b/README.md index abacafe0..8a957a19 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,99 @@ +![license badge](https://img.shields.io/github/license/ColibrITD-SAS/mpqp) +![test status badge](https://img.shields.io/github/actions/workflow/status/ColibrITD-SAS/mpqp/test?label=tests) +![doc status badge](https://img.shields.io/github/actions/workflow/status/ColibrITD-SAS/mpqp/doc?label=doc) +![pipy deployment status badge](https://img.shields.io/github/actions/workflow/status/ColibrITD-SAS/mpqp/pipy?label=pipy) + +![mpqp logo](resources/logo.svg) + # The MPQP library +MPQP stands for Multi-Platform Quantum Programming. It is a python library we at +Colibri felt the need for but couldn't find a solution. We are working on +quantum algorithms, but until now, there was no good solution to study quantum +algorithms across devices, compares the devices, etc... + +MPQP is thus the solution we bring to the community to tackle this problem. + +![mpqp examples](resources/mpqp-usage.gif) + +On this page, you will find: + +1. how to [install](#install) the library; +2. how to [start using](#usage) it; +3. how to [contribute](#contribute) to the development; +4. and the current active [contributors](#contributors). + ## Install -### Install MPQP (and user dependencies) +For now, we support python versions 3.9 to 3.11, and both Windows and Linux +(specifically, if was validated on Ubuntu LTS 20.04, while Ubuntu 18.04 is not +supported). MacOS versions will come very soon! + +The preferred installation method is with the `pipy` repo. In order to use this +installation method, simply run ``` -pip install . +pip install mpqp +``` + +You can also clone this repo and install from source, for instance if you need +to modify something. In that case, we advise you to see the +[Contribute](#contribute) section of this document. + +## Usage + +To get started with MPQP, you can create a quantum circuit with a few gates, and +run it against the backend of your choice: + +```py +from mpqp import QCircuit +from mpqp.gates import * +from mpqp.execution import run, IBMDevice +circ = QCircuit([H(0), H(1), Rx(0,0), CNOT(1,2), Y(2)]) +print(circ) +# ┌───┐┌───────┐ +# q_0: ┤ H ├┤ Rx(0) ├───── +# ├───┤└───────┘ +# q_1: ┤ H ├────■───────── +# └───┘ ┌─┴─┐ ┌───┐ +# q_2: ───────┤ X ├──┤ Y ├ +# └───┘ └───┘ +print(run(circ, IBMDevice.AER_SIMULATOR_STATEVECTOR)) +# Result: IBMDevice, AER_SIMULATOR_STATEVECTOR +# +# State vector: [0.-0.j 0.+0.5j 0.-0.5j 0.+0.j 0.-0.j 0.+0.5j 0.-0.5j 0.+0.j ] +# Probabilities: [0. 0.25 0.25 0. 0. 0.25 0.25 0. ] +# Number of qubits: 3 ``` -### Install dev dependencies +More details are available in our [documentation](https://mpqpdoc.colibri-quantum.com). + +## Contribute + +To contribute, once you cloned this repo, you'll need to install the development +dependencies. ``` pip install -r requirements-dev.txt ``` -### Install only user dependencies +We advise you to get in touch with us on +[our Discord server](https://discord.gg/yyukutWbzf) so we help you on any +difficulty you may encounter along the way. -``` -pip install -r requirements.txt -``` +Two tools are useful when working on MPQP: -## Documentation +- the test suite +- the documentation generation -To generate the website documentation with sphinx +If you need to try out some behaviors of the library during the development, you +can install it from source using the command. ``` -sphinx-build -b html docs build +pip install . ``` -## Tests +### Tests To run the test suite, run the following command: @@ -41,12 +106,36 @@ devs. The full suit can be run by adding the option `-l` or `--long` to the previous command. This should still be run regularly to validate retro compatibility. -TODO: add doctest for doc testing and tox for multiversions testing + + +### Documentation + +The website documentation is generated with +[sphinx](https://www.sphinx-doc.org/en/master/index.html). You probably don't +need to generate it if you work on new features, but if you want to help us by +improving the documentation, you need to know two things: + +- how our documentation is structured, _i.e._ most of it is in the docstrings in + the code. This is done on purpose to keep code and documentation close + together. +- you only need to run one command to generate the documentation: + +``` +sphinx-build -b html docs build +``` + +The changelog is generated from github's versions section. For this to work, you +need to get a github token with public repo read right and save is as an +environment variable with the key `SPHINX_GITHUB_CHANGELOG_TOKEN`. +Alternatively, you can create a `.env` file by duplicating the `.env.example` +one and removing the `.example` extension, and replace in this file the ellipsis +by your token. This said, you don't need the changelog to generate to work on +the documentation. ## Contributors -Henri de Boutray - ColibrITD +Henri de Boutray - ColibriTD -Hamza Jaffali - ColibrITD +Hamza Jaffali - ColibriTD -Muhammad Attallah - ColibrITD \ No newline at end of file +Muhammad Attallah - ColibriTD diff --git a/docs/conf.py b/docs/conf.py index 1bef1000..07ffb392 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,6 @@ from __future__ import annotations from typing import Literal +import dotenv # Configuration file for the Sphinx documentation builder. from sphinx.application import Sphinx @@ -36,14 +37,16 @@ "sphinx.ext.mathjax", "sphinx_rtd_dark_mode", "sphinx_copybutton", - "sphinx_github_changelog", ] default_dark_mode = True autodoc_typehints = "description" autodoc_type_aliases = {"Matrix": "Matrix", "AvailableDevice": "AvailableDevice"} simplify_optional_unions = True typehints_defaults = "comma" -sphinx_github_changelog_token = "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" +dotenv.load_dotenv() +sphinx_github_changelog_token = os.getenv("SPHINX_GITHUB_CHANGELOG_TOKEN") +if sphinx_github_changelog_token is not None: + extensions.append("sphinx_github_changelog") # The suffix of source filenames. source_suffix = ".rst" diff --git a/docs/execution-extras.rst b/docs/execution-extras.rst index 9b7de8b6..3bfd9f75 100644 --- a/docs/execution-extras.rst +++ b/docs/execution-extras.rst @@ -15,7 +15,7 @@ for you are most likely: Provider specifics ------------------ -Even though most of our interfaces use abstractions such that you don't need to +Even though most of our interfaces use abstractions such that you do not need to know on which provider's QPU your code is running, we need at some point to tackle the specifics of each providers. Most (hopefully all soon) of it is tackle in these modules. diff --git a/docs/execution.rst b/docs/execution.rst index 609b0c1d..ce36a5c3 100644 --- a/docs/execution.rst +++ b/docs/execution.rst @@ -6,7 +6,7 @@ Execution from mpqp.execution import * Execution is the core of this library. Our goal is to allow you to run a circuit -on any hardware without you having to rewrite your circuit in the providers's +on any hardware without you having to rewrite your circuit in the providers' ``sdk``. We introduce here how execution works in ``mpqp``, both in local simulator and in remote QPUs. @@ -22,7 +22,7 @@ it regroups methods common to all providers' devices. Then, for each supported quantum computer/simulator provider, we create a specific subclass enumerating all available devices. When devices are retrieved -using a given name (a string), we use it to set the value associated to each key +using a given name (a string), we use it to set the value associated with each key of the :class:`AvailableDevice`. Specific device's info and manipulation are handled by additional methods (sometimes static). @@ -46,16 +46,16 @@ of data needed to setup the connection, summed up here: - Atos/Eviden (Qaptiva/QLM): for this provider, several connection methods exist. For now we only support the username/password method. You should have received you username and password by email; -- AWS (braket): for this provider, you'll need more information: all of them can +- AWS (braket): for this provider, you will need more information: all of them can be found in your `AWS console `_. In the console go to the ``IAM service``, in the ``Users`` tab, click on your username, in the ``Security credential`` tab, you'll find an ``Access keys`` section. In this section, you can create a new access key for MPQP, you should save it because you will not be able to get back your secret latter on. - This will give you your key and your secret, for the configuration you also + This will give you your key and your secret, but for the configuration you also need a region (for example ``us-east-1``). - In short, you'll need: + In short, one would need: + ``AWS Access Key ID``, + ``AWS Secret Access Key`` and diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 4ecc418a..6d58a2b6 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -36,40 +36,46 @@ Setup remote connection ----------------------- After you installed MPQP package using ``pip install``, the script -:mod:`setup_connections.py` can be called from everywhere, not only in -``mpqp`` folder, using the following command line: +:mod:`setup_connections.py` can be +called from everywhere, not only in ``mpqp`` folder, using the following command +line: .. code-block:: console $ setup_connections -This script will allow you to configure and save your personal account to connect to remote machines. -Depending on the provider, different credentials can be asked. Information concerning which provider is configured and -related credentials are stored in the ``~/.mpqp`` file. +This script will allow you to configure and save your personal account to +connect to remote machines. Depending on the provider, different credentials can +be asked. Information concerning which provider is configured and related +credentials are stored in the ``~/.mpqp`` file. IBM Quantum ^^^^^^^^^^^ -Each IBM Quantum account is associated to a unique token. It is accessible by first logging in the -`IBM Quantum Platform `_ and then looking for the ``API Token`` on the top right (that you can -copy). This token is sufficient to configure your account, and to be able to submit job to remote devices. When inputting -you token in the MPQP configuration script, this will configure the account for all your current environment, meaning -that this account will still be configured outside of MPQP. +Each IBM Quantum account is associated to a unique token. It is accessible by +first logging in the `IBM Quantum Platform `_ and then +looking for the ``API Token`` on the top right (that you can copy). This token +is sufficient to configure your account, and to be able to submit jobs to remote +devices. When inputting your token in the MPQP configuration script, this will +configure the account for all your current environments, meaning that this +account will still be configured outside of MPQP. QLMaaS / Qaptiva ^^^^^^^^^^^^^^^^ -QLM propose several way of setting up the account to submit jobs on their simulators. We made the choice to use the -``username`` and ``password`` credentials to identify yourself on the QLM. When configuring the connection with -``setup_connections`` script, we ask you the between configuring the account only in the scope of MPQP, or for your whole -environment. +QLM proposes several ways of setting up the account to submit jobs on their +simulators. We made the choice to use the ``username`` and ``password`` +credentials to identify yourself on the QLM. When configuring the connection +with ``setup_connections`` script, we ask you to choose the between configuring +the account only in the scope of MPQP, or for your whole environment. AWS Braket ^^^^^^^^^^ -For setting up your AWS Braket account, we call the CLI ``aws configure`` that handles it for us. It will ask you your -``AWS Access Key ID``, ``AWS Secret Access Key`` and ``Default region name``. Note that it will configure the account -not only in MPQP scope. +For setting up your AWS Braket account, we call the CLI ``aws configure`` that +handles it for us. It will ask you your ``AWS Access Key ID``, ``AWS Secret +Access Key`` and ``Default region name``. Note that it will configure the +account not only in MPQP scope. Execute examples diff --git a/docs/qasm.rst b/docs/qasm.rst index 3485700b..6f63c925 100644 --- a/docs/qasm.rst +++ b/docs/qasm.rst @@ -20,7 +20,7 @@ From OpenQASM2.0 to OpenQASM3.0 Recently, a new version of OpenQASM (3.0) has been released by conjoint members of IBM Quantum, AWS Quantum Computing, Zapata Computing, Zurich Instruments and -University of Oxford. This version extends the version 2.0, adding some advanced +University of Oxford. This version extends the 2.0 one, adding some advanced features, and modifying parts of syntax and grammar, making some part of OpenQASM2.0 not fully retro-compatible. @@ -30,8 +30,8 @@ can use the function :func:`open_qasm_2_to_3` to translate a code given in parameter as a string. The other facultative parameters are used for recursive calls of the function (when having to translate included files for instance), but are not relevant from a user point of view, expect the parameter -``path_to_file``, useful for locating imports. The translator also convert also -imported files, and include the new ones in the converted code. +``path_to_file``, useful for locating imports. The translator converts also +imported files, and includes the new ones in the converted code. .. autofunction:: mpqp.qasm.open_qasm_2_and_3.open_qasm_2_to_3 @@ -102,7 +102,7 @@ combination of supported standard gates (``rx``, ``ry``, ``rz``, ``cnot``, ``phaseshift`` for instance). Besides, the inclusion of files is not yet handled by Braket library. Therefore, we temporarily created a custom file to *hard-* include (see :ref:`above`) directly in the OpenQASM 3.0 code, to -be sure the parser and interpreter has all definitions in there. We also +be sure the parser and interpreter have all definitions in there. We also hard-include all included files in the OpenQASM 3.0 code inputted for conversion. @@ -113,7 +113,7 @@ conversion. .. autofunction:: mpqp.qasm.qasm_to_braket.qasm3_to_braket_Circuit -In needed, one can also generate a Braket ``Program`` from an OpenQASM 3.0 input +If needed, one can also generate a Braket ``Program`` from an OpenQASM 3.0 input string using the function below. However, in this case, the program parser does not need to redefine the native gates, and thus only performing a hard import of standard gates and other included file is sufficient. However, note that a diff --git a/docs/tools.rst b/docs/tools.rst index b446ce8e..b9ca474b 100644 --- a/docs/tools.rst +++ b/docs/tools.rst @@ -2,11 +2,11 @@ Tools ===== Some additional tools are provided with our library. Even though most of them -are geared at internal usage, there are all presented here. Amongst them, the +are geared at internal usage, they are all presented here. Amongst them, the ones most probable of being of use for you are in: - the section :ref:`viz` presents visualization tools for several data types. - They might be integrated in these types is they are popular enough. + They might be integrated in these types if they are popular enough. - the section :ref:`math` presents mathematical tools for linear algebra, functions generalized to more data types, etc... @@ -52,10 +52,10 @@ relevant, we also append the trace of the error raised by a provider's SDK. Choice Tree ----------- -This module provides functionality for working with decision trees, allowing for +This module provides functionalities for working with decision trees, allowing for seamless navigation and interaction within a tree structure. -The user define a :class:`QuestionNode`, +The user defines a :class:`QuestionNode`, containing a question or requesting and answer from the user. Then the user can select among a list of possible answers, encoded in :class:`AnswerNode`. For each answer, either diff --git a/example/notebooks/Notebook_1__Basics_of_circuit.ipynb b/example/notebooks/Notebook_1__Basics_of_circuit.ipynb index 8ee8afa4..34203a2b 100644 --- a/example/notebooks/Notebook_1__Basics_of_circuit.ipynb +++ b/example/notebooks/Notebook_1__Basics_of_circuit.ipynb @@ -56,7 +56,7 @@ "id": "e04832ad", "metadata": {}, "source": [ - "Then, one can ``add`` at the end of the circuit an instruction or list of instructions. For that, we add some imports." + "Then, one can ``add`` at the end of the circuit an instruction or list of instructions. For that, we add the right imports." ] }, { @@ -172,7 +172,7 @@ "id": "fa694b87", "metadata": {}, "source": [ - "But also use the method ``display`` to render the circuit in different formats. The default one uses matplotlib, but other options can be given in parameters (the same as the ``qiskit`` display function)." + "We can also use the method ``display`` to render the circuit in different formats. The default one uses matplotlib, but other options can be given in parameters (the same as the ``qiskit`` display function)." ] }, { @@ -234,7 +234,7 @@ "source": [ "## Retrieve circuit properties\n", "\n", - "Depth blabla" + "One can also retrieve different properties of the circuit, including the depth, the size (quantum and classical bits), counting (specific) gates and retrieving the attached measurements." ] }, { @@ -328,7 +328,7 @@ "source": [ "## Combination of circuits\n", "\n", - "babpabababllba" + "We also allow to combine easily different circuits to create new ones. " ] }, { @@ -366,7 +366,7 @@ "id": "68ef12e4", "metadata": {}, "source": [ - "``append``" + "One can ``append`` a circuit to another, consisting in concatenating the circuit horizontally at the right of the first one. For more simplicity, the ``+`` realized the same operation. When the size does not match, we automatically adjust the size of the resulting circuit." ] }, { @@ -397,7 +397,7 @@ "id": "c70512be", "metadata": {}, "source": [ - "``tensor``" + "One can also take the tensor product of two circuits using the method ``tensor``, or the symbol ``@``, which will concatenate the second circuit vertically under the first." ] }, { @@ -430,7 +430,9 @@ "id": "54598610", "metadata": {}, "source": [ - "## Translating the circuit" + "## Translating the circuit\n", + "\n", + "MPQP is a multi-platform quantum programming library and handles for the user the translation of circuits. We allow the user to use a function to export the circuit in different objects specific to each SDK using the method ``to_other_language``. " ] }, { @@ -442,7 +444,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 17, @@ -459,12 +461,14 @@ "cell_type": "code", "execution_count": 18, "id": "2869a46e", - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [ { "data": { "text/plain": [ - "Circuit(ops=[Op(gate='H', qbits=[0], type=0, cbits=None, formula=None, remap=None), Op(gate='X', qbits=[1], type=0, cbits=None, formula=None, remap=None), Op(gate='CNOT', qbits=[1, 2], type=0, cbits=None, formula=None, remap=None), Op(gate='Y', qbits=[2], type=0, cbits=None, formula=None, remap=None), Op(gate='Z', qbits=[0], type=0, cbits=None, formula=None, remap=None), Op(gate='CSIGN', qbits=[1, 0], type=0, cbits=None, formula=None, remap=None)], name=None, gateDic={'X': GateDefinition(name='X', arity=1, matrix=Matrix(nRows=2, nCols=2, data=[ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0)]), is_ctrl=False, is_dag=None, is_trans=None, is_conj=None, subgate=None, syntax=GSyntax(name='X', parameters=[]), nbctrls=None, circuit_implementation=None), 'Y': GateDefinition(name='Y', arity=1, matrix=Matrix(nRows=2, nCols=2, data=[ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=-0.0, im=-1.0), ComplexNumber(re=0.0, im=1.0), ComplexNumber(re=0.0, im=0.0)]), is_ctrl=False, is_dag=None, is_trans=None, is_conj=None, subgate=None, syntax=GSyntax(name='Y', parameters=[]), nbctrls=None, circuit_implementation=None), 'Z': GateDefinition(name='Z', arity=1, matrix=Matrix(nRows=2, nCols=2, data=[ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=-1.0, im=0.0)]), is_ctrl=False, is_dag=None, is_trans=None, is_conj=None, subgate=None, syntax=GSyntax(name='Z', parameters=[]), nbctrls=None, circuit_implementation=None), 'H': GateDefinition(name='H', arity=1, matrix=Matrix(nRows=2, nCols=2, data=[ComplexNumber(re=0.7071067811865475, im=0.0), ComplexNumber(re=0.7071067811865475, im=0.0), ComplexNumber(re=0.7071067811865475, im=0.0), ComplexNumber(re=-0.7071067811865475, im=0.0)]), is_ctrl=False, is_dag=None, is_trans=None, is_conj=None, subgate=None, syntax=GSyntax(name='H', parameters=[]), nbctrls=None, circuit_implementation=None), 'CNOT': GateDefinition(name='CNOT', arity=2, matrix=Matrix(nRows=4, nCols=4, data=[ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0)]), is_ctrl=True, is_dag=None, is_trans=None, is_conj=None, subgate='X', syntax=GSyntax(name='CNOT', parameters=[]), nbctrls=1, circuit_implementation=None), 'CSIGN': GateDefinition(name='CSIGN', arity=2, matrix=Matrix(nRows=4, nCols=4, data=[ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=-1.0, im=0.0)]), is_ctrl=True, is_dag=None, is_trans=None, is_conj=None, subgate='Z', syntax=GSyntax(name='CSIGN', parameters=[]), nbctrls=1, circuit_implementation=None), 'ISWAP': GateDefinition(name='ISWAP', arity=2, matrix=Matrix(nRows=4, nCols=4, data=[ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=1.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=1.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0)]), is_ctrl=False, is_dag=None, is_trans=None, is_conj=None, subgate=None, syntax=GSyntax(name='ISWAP', parameters=[]), nbctrls=None, circuit_implementation=None), 'SQRTSWAP': GateDefinition(name='SQRTSWAP', arity=2, matrix=Matrix(nRows=4, nCols=4, data=[ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.5, im=0.5), ComplexNumber(re=0.5, im=-0.5), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.5, im=-0.5), ComplexNumber(re=0.5, im=0.5), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0)]), is_ctrl=False, is_dag=None, is_trans=None, is_conj=None, subgate=None, syntax=GSyntax(name='SQRTSWAP', parameters=[]), nbctrls=None, circuit_implementation=None), 'I': GateDefinition(name='I', arity=1, matrix=Matrix(nRows=2, nCols=2, data=[ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0)]), is_ctrl=False, is_dag=None, is_trans=None, is_conj=None, subgate=None, syntax=GSyntax(name='I', parameters=[]), nbctrls=None, circuit_implementation=None), 'SWAP': GateDefinition(name='SWAP', arity=2, matrix=Matrix(nRows=4, nCols=4, data=[ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0)]), is_ctrl=False, is_dag=None, is_trans=None, is_conj=None, subgate=None, syntax=GSyntax(name='SWAP', parameters=[]), nbctrls=None, circuit_implementation=None), 'CCNOT': GateDefinition(name='CCNOT', arity=3, matrix=Matrix(nRows=8, nCols=8, data=[ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0)]), is_ctrl=True, is_dag=None, is_trans=None, is_conj=None, subgate='CNOT', syntax=GSyntax(name='CCNOT', parameters=[]), nbctrls=1, circuit_implementation=None), 'S': GateDefinition(name='S', arity=1, matrix=Matrix(nRows=2, nCols=2, data=[ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=1.0)]), is_ctrl=False, is_dag=None, is_trans=None, is_conj=None, subgate=None, syntax=GSyntax(name='S', parameters=[]), nbctrls=None, circuit_implementation=None), 'T': GateDefinition(name='T', arity=1, matrix=Matrix(nRows=2, nCols=2, data=[ComplexNumber(re=1.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.0, im=0.0), ComplexNumber(re=0.7071067811865476, im=0.7071067811865476)]), is_ctrl=False, is_dag=None, is_trans=None, is_conj=None, subgate=None, syntax=GSyntax(name='T', parameters=[]), nbctrls=None, circuit_implementation=None)}, nbqbits=3, nbcbits=0, _gate_set=GateSet(gate_signatures={'X': GateSignature(name='X', parameters=[], arity=1, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'Y': GateSignature(name='Y', parameters=[], arity=1, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'Z': GateSignature(name='Z', parameters=[], arity=1, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'H': GateSignature(name='H', parameters=[], arity=1, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'CNOT': GateSignature(name='CNOT', parameters=[], arity=2, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'CSIGN': GateSignature(name='CSIGN', parameters=[], arity=2, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'ISWAP': GateSignature(name='ISWAP', parameters=[], arity=2, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'SQRTSWAP': GateSignature(name='SQRTSWAP', parameters=[], arity=2, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'I': GateSignature(name='I', parameters=[], arity=1, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'SWAP': GateSignature(name='SWAP', parameters=[], arity=2, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'CCNOT': GateSignature(name='CCNOT', parameters=[], arity=3, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'S': GateSignature(name='S', parameters=[], arity=1, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'T': GateSignature(name='T', parameters=[], arity=1, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'PH': GateSignature(name='PH', parameters=[1], arity=1, nb_args=1, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'RZ': GateSignature(name='RZ', parameters=[1], arity=1, nb_args=1, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'RX': GateSignature(name='RX', parameters=[1], arity=1, nb_args=1, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'RY': GateSignature(name='RY', parameters=[1], arity=1, nb_args=1, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'U': AbstractGate(name='U', parameters=[1, 1, 1], arity=1, nb_args=3, arg_types=[, , ], arity_generator=None, matrix_generator=, circuit_generator=None, dag_func=None), 'U1': AbstractGate(name='U1', parameters=[1], arity=1, nb_args=1, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None, dag_func=None), 'U2': AbstractGate(name='U2', parameters=[1, 1], arity=1, nb_args=2, arg_types=[, ], arity_generator=None, matrix_generator=, circuit_generator=None, dag_func=None), 'U3': AbstractGate(name='U3', parameters=[1, 1, 1], arity=1, nb_args=3, arg_types=[, , ], arity_generator=None, matrix_generator=, circuit_generator=None, dag_func=None)}), has_matrices=True, var_dic={}, qregs=[], ancilla_map=None, _serialized_gate_set=b\"\\x80\\x04\\x95?\\x06\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x11qat.core.gate_set\\x94\\x8c\\x07GateSet\\x94\\x93\\x94)\\x81\\x94}\\x94\\x8c\\x0fgate_signatures\\x94}\\x94(\\x8c\\x01X\\x94h\\x00\\x8c\\rGateSignature\\x94\\x93\\x94)\\x81\\x94}\\x94(\\x8c\\x04name\\x94h\\x07\\x8c\\nparameters\\x94]\\x94\\x8c\\x05arity\\x94K\\x01\\x8c\\x07nb_args\\x94K\\x00\\x8c\\targ_types\\x94]\\x94\\x8c\\x0farity_generator\\x94N\\x8c\\x10matrix_generator\\x94\\x8c$qat.core.circuit_builder.matrix_util\\x94\\x8c\\x05gen_x\\x94\\x93\\x94\\x8c\\x11circuit_generator\\x94Nub\\x8c\\x01Y\\x94h\\t)\\x81\\x94}\\x94(h\\x0ch\\x19h\\r]\\x94h\\x0fK\\x01h\\x10K\\x00h\\x11]\\x94h\\x13Nh\\x14h\\x15\\x8c\\x05gen_y\\x94\\x93\\x94h\\x18Nub\\x8c\\x01Z\\x94h\\t)\\x81\\x94}\\x94(h\\x0ch h\\r]\\x94h\\x0fK\\x01h\\x10K\\x00h\\x11]\\x94h\\x13Nh\\x14h\\x15\\x8c\\x05gen_z\\x94\\x93\\x94h\\x18Nub\\x8c\\x01H\\x94h\\t)\\x81\\x94}\\x94(h\\x0ch'h\\r]\\x94h\\x0fK\\x01h\\x10K\\x00h\\x11]\\x94h\\x13Nh\\x14h\\x15\\x8c\\x05gen_h\\x94\\x93\\x94h\\x18Nub\\x8c\\x04CNOT\\x94h\\t)\\x81\\x94}\\x94(h\\x0ch.h\\r]\\x94h\\x0fK\\x02h\\x10K\\x00h\\x11]\\x94h\\x13Nh\\x14h\\x15\\x8c\\x08gen_cnot\\x94\\x93\\x94h\\x18Nub\\x8c\\x05CSIGN\\x94h\\t)\\x81\\x94}\\x94(h\\x0ch5h\\r]\\x94h\\x0fK\\x02h\\x10K\\x00h\\x11]\\x94h\\x13Nh\\x14h\\x15\\x8c\\tgen_csign\\x94\\x93\\x94h\\x18Nub\\x8c\\x05ISWAP\\x94h\\t)\\x81\\x94}\\x94(h\\x0ch, circuit_generator=None), 'Y': GateSignature(name='Y', parameters=[], arity=1, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'Z': GateSignature(name='Z', parameters=[], arity=1, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'H': GateSignature(name='H', parameters=[], arity=1, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'CNOT': GateSignature(name='CNOT', parameters=[], arity=2, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'CSIGN': GateSignature(name='CSIGN', parameters=[], arity=2, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'ISWAP': GateSignature(name='ISWAP', parameters=[], arity=2, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'SQRTSWAP': GateSignature(name='SQRTSWAP', parameters=[], arity=2, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'I': GateSignature(name='I', parameters=[], arity=1, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'SWAP': GateSignature(name='SWAP', parameters=[], arity=2, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'CCNOT': GateSignature(name='CCNOT', parameters=[], arity=3, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'S': GateSignature(name='S', parameters=[], arity=1, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'T': GateSignature(name='T', parameters=[], arity=1, nb_args=0, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'PH': GateSignature(name='PH', parameters=[1], arity=1, nb_args=1, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'RZ': GateSignature(name='RZ', parameters=[1], arity=1, nb_args=1, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'RX': GateSignature(name='RX', parameters=[1], arity=1, nb_args=1, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'RY': GateSignature(name='RY', parameters=[1], arity=1, nb_args=1, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None), 'U': AbstractGate(name='U', parameters=[1, 1, 1], arity=1, nb_args=3, arg_types=[, , ], arity_generator=None, matrix_generator=, circuit_generator=None, dag_func=None), 'U1': AbstractGate(name='U1', parameters=[1], arity=1, nb_args=1, arg_types=[], arity_generator=None, matrix_generator=, circuit_generator=None, dag_func=None), 'U2': AbstractGate(name='U2', parameters=[1, 1], arity=1, nb_args=2, arg_types=[, ], arity_generator=None, matrix_generator=, circuit_generator=None, dag_func=None), 'U3': AbstractGate(name='U3', parameters=[1, 1, 1], arity=1, nb_args=3, arg_types=[, , ], arity_generator=None, matrix_generator=, circuit_generator=None, dag_func=None)}), has_matrices=True, var_dic={}, qregs=[], ancilla_map=None, _serialized_gate_set=b\"\\x80\\x04\\x95?\\x06\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x11qat.core.gate_set\\x94\\x8c\\x07GateSet\\x94\\x93\\x94)\\x81\\x94}\\x94\\x8c\\x0fgate_signatures\\x94}\\x94(\\x8c\\x01X\\x94h\\x00\\x8c\\rGateSignature\\x94\\x93\\x94)\\x81\\x94}\\x94(\\x8c\\x04name\\x94h\\x07\\x8c\\nparameters\\x94]\\x94\\x8c\\x05arity\\x94K\\x01\\x8c\\x07nb_args\\x94K\\x00\\x8c\\targ_types\\x94]\\x94\\x8c\\x0farity_generator\\x94N\\x8c\\x10matrix_generator\\x94\\x8c$qat.core.circuit_builder.matrix_util\\x94\\x8c\\x05gen_x\\x94\\x93\\x94\\x8c\\x11circuit_generator\\x94Nub\\x8c\\x01Y\\x94h\\t)\\x81\\x94}\\x94(h\\x0ch\\x19h\\r]\\x94h\\x0fK\\x01h\\x10K\\x00h\\x11]\\x94h\\x13Nh\\x14h\\x15\\x8c\\x05gen_y\\x94\\x93\\x94h\\x18Nub\\x8c\\x01Z\\x94h\\t)\\x81\\x94}\\x94(h\\x0ch h\\r]\\x94h\\x0fK\\x01h\\x10K\\x00h\\x11]\\x94h\\x13Nh\\x14h\\x15\\x8c\\x05gen_z\\x94\\x93\\x94h\\x18Nub\\x8c\\x01H\\x94h\\t)\\x81\\x94}\\x94(h\\x0ch'h\\r]\\x94h\\x0fK\\x01h\\x10K\\x00h\\x11]\\x94h\\x13Nh\\x14h\\x15\\x8c\\x05gen_h\\x94\\x93\\x94h\\x18Nub\\x8c\\x04CNOT\\x94h\\t)\\x81\\x94}\\x94(h\\x0ch.h\\r]\\x94h\\x0fK\\x02h\\x10K\\x00h\\x11]\\x94h\\x13Nh\\x14h\\x15\\x8c\\x08gen_cnot\\x94\\x93\\x94h\\x18Nub\\x8c\\x05CSIGN\\x94h\\t)\\x81\\x94}\\x94(h\\x0ch5h\\r]\\x94h\\x0fK\\x02h\\x10K\\x00h\\x11]\\x94h\\x13Nh\\x14h\\x15\\x8c\\tgen_csign\\x94\\x93\\x94h\\x18Nub\\x8c\\x05ISWAP\\x94h\\t)\\x81\\x94}\\x94(h\\x0ch>> circuit = QCircuit(2) >>> circuit.pretty_print() QCircuit : Size (Qubits,Cbits) = (2, 0), Nb instructions = 0 @@ -63,21 +63,21 @@ class QCircuit: q_4: ──────────── Args: - data: Number of qubits or List of instructions to initiate the circuit - with. If the number of qubits is passed, it should be a positive int. - nb_qubits: Optional number of qubits, in case you input the sequence - of instruction and want to hardcode the number of qubits. - nb_cbits: Number of classical bits. It should be positive. Defaults to - None. label: Name of the circuit. Defaults to None. + data: Number of qubits or List of instructions to initiate the circuit with. If the number of qubits is passed, + it should be a positive int. + nb_qubits: Optional number of qubits, in case you input the sequence of instruction and want to hardcode the + number of qubits. + nb_cbits: Number of classical bits. It should be positive. Defaults to None. + label: Name of the circuit. Defaults to None. """ def __init__( - self, - data: int | Sequence[Instruction], - *, - nb_qubits: Optional[int] = None, - nb_cbits: Optional[int] = None, - label: Optional[str] = None, + self, + data: int | Sequence[Instruction], + *, + nb_qubits: Optional[int] = None, + nb_cbits: Optional[int] = None, + label: Optional[str] = None, ): self.nb_cbits = nb_cbits """See parameter description.""" @@ -115,7 +115,7 @@ def add(self, instruction: Instruction | Iterable[Instruction]): Args: instruction : Instruction(s) to append at the end of the circuit. - Examples: + Example: >>> circuit = QCircuit(2) >>> circuit.add(X(0)) >>> circuit.add([CNOT(0, 1), BasisMeasure([0, 1], shots=100)]) @@ -160,13 +160,13 @@ def add(self, instruction: Instruction | Iterable[Instruction]): self.instructions.append(instruction) def append(self, other: QCircuit, qubits_offset: int = 0) -> None: - """Append the circuit at the end (right side) of this circuit, inplace. + """Appends the circuit at the end (right side) of this circuit, inplace. - If the size of the circuit in parameter is smaller than this circuit, + If the size of the ``other`` is smaller than this circuit, the parameter ``qubits_offset`` can be used to indicate at which qubit the ``other`` circuit must be added. - Examples: + Example: >>> c1 = QCircuit([CNOT(0,1),CNOT(1,2)]) >>> c2 = QCircuit([X(1),CNOT(1,2)]) >>> c1.append(c2) @@ -221,13 +221,13 @@ def __add__(self, other: QCircuit) -> QCircuit: return res def tensor(self, other: QCircuit) -> QCircuit: - """Compute the tensor product of this circuit with the one in parameter. + """Computes the tensor product of this circuit with the one in parameter. - In the circuit notation, the upper part of the circuit will correspond - to this circuit, while the bottom part correspond to the circuit in + In the circuit notation, the upper part of the output circuit will correspond + to the first circuit, while the bottom part correspond to the one in parameter. - Examples: + Example: >>> c1 = QCircuit([CNOT(0,1),CNOT(1,2)]) >>> c2 = QCircuit([X(1),CNOT(1,2)]) >>> print(c1.tensor(c2)) @@ -243,12 +243,10 @@ def tensor(self, other: QCircuit) -> QCircuit: └───┘ Args: - other: QCircuit being the second operand of the tensor product with - this circuit. + other: QCircuit being the second operand of the tensor product with this circuit. Returns: - The QCircuit resulting of the tensor product of this circuit with - the one in parameter. + The QCircuit resulting from the tensor product of this circuit with the one in parameter. """ res = deepcopy(self) res.nb_qubits += other.nb_qubits @@ -264,7 +262,7 @@ def display(self, output: str = "mpl"): For now, this uses the qiskit circuit drawer, so all formats supported by qiskit are supported. - Examples: + Example: >>> theta = symbols("θ") >>> circ = QCircuit([ ... P(theta, 0), @@ -299,7 +297,7 @@ def display(self, output: str = "mpl"): return fig def size(self) -> tuple[int, int]: - """Provide the size of the circuit, in terms of number of quantum and + """Provides the size of the circuit, in terms of number of quantum and classical bits. Examples: @@ -314,13 +312,13 @@ def size(self) -> tuple[int, int]: (3, 3) Returns: - A couple (q, c) of integers, with q the number of qubits, and c the - number of cbits of this circuit. + A couple ``(q, c)`` of integers, with ``q`` the number of qubits, + and ``c`` the number of cbits of this circuit. """ return self.nb_qubits, (self.nb_cbits or 0) def depth(self) -> int: - """Compute the depth of the circuit. + """Computes the depth of the circuit. Example: >>> QCircuit([CNOT(0, 1), CNOT(1, 2), CNOT(0, 1), X(2)]).depth() @@ -360,7 +358,7 @@ def depth(self) -> int: def __len__(self) -> int: """Returns the number of instructions added to this circuit. - Examples: + Example: >>> c1 = QCircuit([CNOT(0,1), CNOT(1,2), X(1), CNOT(1,2)]) >>> len(c1) 4 @@ -377,7 +375,7 @@ def is_equivalent(self, circuit: QCircuit) -> bool: Depending on the definition of the gates of the circuit, several methods could be used to do it in an optimized way. - Examples: + Example: >>> c1 = QCircuit([H(0), H(0)]) >>> c2 = QCircuit([Rx(0, 0)]) >>> c1.is_equivalent(c2) @@ -388,7 +386,7 @@ def is_equivalent(self, circuit: QCircuit) -> bool: to this circuit. Returns: - True if the circuit in parameter is equivalent to this circuit + ``True`` if the circuit in parameter is equivalent to this circuit 3M-TODO: will only work once the circuit.to_matrix is implemented """ @@ -520,7 +518,7 @@ def initializer(cls, state: npt.NDArray[np.complex64]) -> QCircuit: # qiskit QuantumCircuit feature qc.initialize() """ size = int(np.log2(len(state))) - if 2 ** size != len(state): + if 2**size != len(state): raise ValueError(f"Input state {state} should have a power of 2 size") res = cls(size) ... @@ -554,9 +552,9 @@ def count_gates(self, gate: Optional[Type[Gate]] = None) -> int: return len([inst for inst in self.instructions if isinstance(inst, filter2)]) def get_measurements(self) -> list[Measure]: - """Returns all the measurement present in this circuit. + """Returns all the measurements present in this circuit. - Examples: + Example: >>> circuit = QCircuit([ ... BasisMeasure([0, 1], shots=1000), ... ExpectationMeasure([1], Observable(np.identity(2)), shots=1000) @@ -571,9 +569,9 @@ def get_measurements(self) -> list[Measure]: return [inst for inst in self.instructions if isinstance(inst, Measure)] def without_measurements(self) -> QCircuit: - """Provide a copy of this circuit with all the measurement removed. + """Provides a copy of this circuit with all the measurements removed. - Examples: + Example: >>> circuit = QCircuit([X(0), CNOT(0, 1), BasisMeasure([0, 1], shots=100)]) >>> print(circuit) ┌───┐ ┌─┐ @@ -600,9 +598,10 @@ def without_measurements(self) -> QCircuit: return new_circuit - def to_other_language(self, language: Language = Language.QISKIT - ) -> Union[QuantumCircuit, myQLM_Circuit, braket_Circuit]: - """Transform this circuit into the corresponding circuit in the language + def to_other_language( + self, language: Language = Language.QISKIT + ) -> Union[QuantumCircuit, myQLM_Circuit, braket_Circuit]: + """Transforms this circuit into the corresponding circuit in the language specified in the ``language`` arg. By default, the circuit is translated to the corresponding @@ -613,7 +612,7 @@ def to_other_language(self, language: Language = Language.QISKIT method will be used only for complex objects that are not tractable by OpenQASM (like hybrid structures). - Examples: + Example: >>> circuit = QCircuit([X(0), CNOT(0, 1)]) >>> qc = circuit.to_other_language() >>> type(qc) @@ -653,7 +652,7 @@ def to_other_language(self, language: Language = Language.QISKIT elif isinstance(instruction, Gate): qargs = instruction.targets elif isinstance(instruction, BasisMeasure) and isinstance( - instruction.basis, ComputationalBasis + instruction.basis, ComputationalBasis ): assert instruction.c_targets is not None qargs = [instruction.targets] @@ -684,12 +683,12 @@ def to_other_language(self, language: Language = Language.QISKIT raise NotImplementedError(f"Error: {language} is not supported") def to_qasm2(self) -> str: - """Convert this circuit to the corresponding OpenQASM 2 code. + """Converts this circuit to the corresponding OpenQASM 2 code. For now, we use an intermediate conversion to a Qiskit ``QuantumCircuit``. - Examples: + Example: >>> circuit = QCircuit([X(0), CNOT(0, 1), BasisMeasure([0, 1], shots=100)]) >>> print(circuit.to_qasm2()) OPENQASM 2.0; @@ -705,21 +704,21 @@ def to_qasm2(self) -> str: A string representing the OpenQASM2 code corresponding to this circuit. """ - qasm = ( - self.subs({}, remove_symbolic=True) - .to_other_language(Language.QISKIT) - .qasm() + qiskit_circ = self.subs({}, remove_symbolic=True).to_other_language( + Language.QISKIT ) + assert isinstance(qiskit_circ, QuantumCircuit) + qasm = qiskit_circ.qasm() assert qasm is not None return qasm def to_qasm3(self) -> str: - """Convert this circuit to the corresponding OpenQASM 3 code. + """Converts this circuit to the corresponding OpenQASM 3 code. For now, we use an intermediate conversion to OpenQASM 2, and then a converter from 2 to 3. - Examples: + Example: >>> circuit = QCircuit([X(0), CNOT(0, 1), BasisMeasure([0, 1], shots=100)]) >>> print(circuit.to_qasm3()) OPENQASM 3.0; @@ -740,7 +739,7 @@ def to_qasm3(self) -> str: return qasm3_code def subs( - self, values: dict[Expr | str, Complex], remove_symbolic: bool = False + self, values: dict[Expr | str, Complex], remove_symbolic: bool = False ) -> QCircuit: r"""Substitute the parameters of the circuit with complex values. Optionally also remove all symbolic variables such as `\pi` (needed for @@ -794,7 +793,7 @@ def subs( def pretty_print(self): """Provides a pretty print of the QCircuit. - Examples: + Example: >>> c = QCircuit([H(0), CNOT(0,1)]) >>> c.pretty_print() QCircuit : Size (Qubits,Cbits) = (2, None), Nb instructions = 2 diff --git a/mpqp/core/instruction/gates/gate.py b/mpqp/core/instruction/gates/gate.py index 2b45aff3..f2b8b1bc 100644 --- a/mpqp/core/instruction/gates/gate.py +++ b/mpqp/core/instruction/gates/gate.py @@ -62,7 +62,7 @@ def to_matrix(self) -> Matrix: def inverse(self) -> Gate: """Computing the inverse of this gate. - Examples: + Example: >>> Z(0).inverse() Z(0) >>> CustomGate(UnitaryMatrix(np.diag([1,1j])),[0]).inverse().to_matrix() @@ -87,7 +87,7 @@ def is_equivalent(self, other: Gate) -> bool: semantics (and thus ignores all other aspects of the gate such as the target qubits, the label, etc....) - Examples: + Example: >>> X(0).is_equivalent(CustomGate(UnitaryMatrix(np.array([[0,1],[1,0]])),[1])) True @@ -95,7 +95,7 @@ def is_equivalent(self, other: Gate) -> bool: other: the gate to test if it is equivalent to this gate Returns: - True if the two gates' matrix semantics are equal. + ``True`` if the two gates' matrix semantics are equal. """ return matrix_eq(self.to_matrix(), other.to_matrix()) @@ -143,7 +143,7 @@ def power(self, exponent: float) -> Gate: def tensor_product(self, other: Gate) -> Gate: """Compute the tensor product of the current gate. - Examples: + Example: >>> (X(0).tensor_product(Z(0))).to_matrix() array([[ 0, 0, 1, 0], [ 0, 0, 0, -1], @@ -185,7 +185,7 @@ def _mandatory_label(self, postfix: str = ""): def product(self, other: Gate, targets: Optional[list[int]] = None) -> Gate: """Compute the composition of self and the other gate. - Examples: + Example: >>> (X(0).product(Z(0))).to_matrix() array([[ 0, -1], [ 1, 0]]) @@ -211,7 +211,7 @@ def scalar_product(self, scalar: complex) -> Gate: """Multiply this gate by a scalar. It normalizes the subtraction to ensure it is unitary. - Examples: + Example: >>> (X(0).scalar_product(1j)).to_matrix() array([[0, 1j], [1j, 0]]) @@ -235,7 +235,7 @@ def minus(self, other: Gate, targets: Optional[list[int]] = None) -> Gate: """Compute the subtraction of two gates. It normalizes the subtraction to ensure it is unitary. - Examples: + Example: >>> (X(0).minus(Z(0))).to_matrix() array([[-0.70710678, 0.70710678], [ 0.70710678, 0.70710678]]) @@ -263,7 +263,7 @@ def plus(self, other: Gate, targets: Optional[list[int]] = None) -> Gate: """Compute the sum of two gates. It normalizes the subtraction to ensure it is unitary. - Examples: + Example: >>> (X(0).plus(Z(0))).to_matrix() array([[ 0.70710678, 0.70710678], [ 0.70710678, -0.70710678]]) diff --git a/mpqp/core/instruction/gates/gate_definition.py b/mpqp/core/instruction/gates/gate_definition.py index 53b12b7f..58546790 100644 --- a/mpqp/core/instruction/gates/gate_definition.py +++ b/mpqp/core/instruction/gates/gate_definition.py @@ -23,7 +23,7 @@ class GateDefinition(ABC): This said, for now only one way of defining the gates is supported, using their matricial semantics. - Examples: + Example: >>> gate_matrix = np.array([[0, 0, 0, 1], [0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 1, 0]]) >>> gate_definition = UnitaryMatrix(gate_matrix) >>> custom_gate = CustomGate(gate_definition) @@ -66,7 +66,7 @@ def is_equivalent(self, other: GateDefinition) -> bool: Args: other: The definition we want to know if it is equivalent. - Examples: + Example: >>> d1 = UnitaryMatrix(np.array([[1, 0], [0, -1]])) >>> d2 = UnitaryMatrix(np.array([[2, 0], [0, -2.0]]) / 2) >>> d1.is_equivalent(d2) @@ -80,7 +80,7 @@ def inverse(self) -> GateDefinition: Returns: A GateDefinition representing the inverse of the gate defined. - Examples: + Example: >>> UnitaryMatrix(np.array([[1, 0], [0, -1]])).inverse() array([[ 1., 0.], [-0., -1.]]) @@ -172,12 +172,12 @@ def caster(v: Expr | Complex) -> Expr | Complex: # "complex" cannot be assigned to return type "Complex" return ( complex(v) if remove_symbolic else v - ) # pyright: ignore[reportGeneralTypeIssues] + ) # pyright: ignore[reportReturnType] # the types in sympy are relatively badly handled # Argument of type "Unknown | Basic | Expr" cannot be assigned to parameter "v" of type "Expr | Complex" return ( - caster(val.subs(values)) # pyright: ignore[reportGeneralTypeIssues] + caster(val.subs(values)) # pyright: ignore[reportArgumentType] if isinstance(val, Expr) else val ) diff --git a/mpqp/core/instruction/gates/parametrized_gate.py b/mpqp/core/instruction/gates/parametrized_gate.py index 5065d2e2..a0f610cc 100644 --- a/mpqp/core/instruction/gates/parametrized_gate.py +++ b/mpqp/core/instruction/gates/parametrized_gate.py @@ -39,7 +39,7 @@ class ParametrizedGate(Gate, ABC): parameters: List of parameters used to define the gate. label: Label used to identify the measurement. - Examples: + Example: >>> theta = np.pi/3 >>> c, s = np.cos(theta / 2), np.sin(theta / 2) >>> gate_def = UnitaryMatrix(np.array([[c, s], [-s, c]])) diff --git a/mpqp/core/instruction/instruction.py b/mpqp/core/instruction/instruction.py index df16d13a..127da54a 100644 --- a/mpqp/core/instruction/instruction.py +++ b/mpqp/core/instruction/instruction.py @@ -72,7 +72,7 @@ def to_other_language( language: Language = Language.QISKIT, qiskit_parameters: Optional[set[Parameter]] = None, ) -> Any: - """Transform this instruction into the corresponding object in the + """Transforms this instruction into the corresponding object in the language specified in the ``language`` arg. By default, the instruction is translated to the corresponding one in @@ -130,8 +130,8 @@ def connections(self) -> set[int]: def subs( self, values: dict[Expr | str, Complex], remove_symbolic: bool = False ) -> Instruction: - r"""Substitute the parameters of the instruction with complex values. - Optionally also remove all symbolic variables such as `\pi` (needed for + r"""Substitutes the parameters of the instruction with complex values. + Optionally also removes all symbolic variables such as `\pi` (needed for example for circuit execution). Since we use ``sympy`` for gates' parameters, ``values`` can in fact be diff --git a/mpqp/core/instruction/measurement/basis.py b/mpqp/core/instruction/measurement/basis.py index 7c38c025..942bcaf6 100644 --- a/mpqp/core/instruction/measurement/basis.py +++ b/mpqp/core/instruction/measurement/basis.py @@ -30,7 +30,7 @@ class Basis: specified, it will be automatically inferred from ``basis_vectors``'s dimensions. - Examples: + Example: >>> Basis([np.array([1,0]), np.array([0,-1])]).pretty_print() Basis: [ [1 0], @@ -175,7 +175,7 @@ class HadamardBasis(VariableSizeBasis): Args: nb_qubits: number of qubits in the basis - Examples: + Example: >>> HadamardBasis(2).pretty_print() Basis: [ [0.5+0.j 0.5+0.j 0.5+0.j 0.5+0.j], diff --git a/mpqp/core/instruction/measurement/expectation_value.py b/mpqp/core/instruction/measurement/expectation_value.py index 61aec5e1..593a23a5 100644 --- a/mpqp/core/instruction/measurement/expectation_value.py +++ b/mpqp/core/instruction/measurement/expectation_value.py @@ -33,7 +33,7 @@ class Observable: For the moment, on can only define the observable using a matrix. - Examples: + Example: >>> matrix = np.array([[1, 0], [0, -1]]) >>> obs = Observable(matrix) @@ -84,7 +84,7 @@ class ExpectationMeasure(Measure): hardware. The swaps added can be checked out in the ``pre_measure`` attribute of the :class:`ExpectationMeasure`. - Examples: + Example: >>> obs = Observable(np.diag([0.7, -1, 1, 1])) >>> c = QCircuit([H(0), CNOT(0,1), ExpectationMeasure([0,1], observable=obs, shots=10000)]) >>> run(c, ATOSDevice.MYQLM_PYLINALG).expectation_value diff --git a/mpqp/execution/connection/aws_connection.py b/mpqp/execution/connection/aws_connection.py index 4c01673c..54c9d1fd 100644 --- a/mpqp/execution/connection/aws_connection.py +++ b/mpqp/execution/connection/aws_connection.py @@ -14,7 +14,7 @@ def setup_aws_braket_account() -> tuple[str, list[Any]]: - """Set up the connection to an Amazon Braket account using user input. + """Setups the connection to an Amazon Braket account using user input. This function checks whether an Amazon Braket account is already configured and prompts the user to update it if needed. It then collects the user's AWS @@ -53,7 +53,7 @@ def get_aws_braket_account_info() -> str: """Get AWS Braket credentials information including access key ID, obfuscated secret access key, and region. - Examples: + Example: >>> get_aws_braket_account_info() access_key_id: 'AKIA26NYJ***********' secret_access_key: 'sMDad***********************************' @@ -95,10 +95,7 @@ def get_braket_device(device: AWSDevice) -> BraketDevice: """ Returns the AwsDevice device associate with the AWSDevice in parameter. - Args: - device: AWSDevice element describing which remote/local AwsDevice we want. - - Examples: + Example: >>> device = get_braket_device(AWSDevice.BRAKET_RIGETTI_ASPEN_M_3) >>> device.properties.action['braket.ir.jaqcd.program'].supportedResultTypes [ResultType(name='Sample', observables=['x', 'y', 'z', 'h', 'i'], minShots=10, maxShots=100000), @@ -106,6 +103,9 @@ def get_braket_device(device: AWSDevice) -> BraketDevice: ResultType(name='Variance', observables=['x', 'y', 'z', 'h', 'i'], minShots=10, maxShots=100000), ResultType(name='Probability', observables=None, minShots=10, maxShots=100000)] + Args: + device: AWSDevice element describing which remote/local AwsDevice we want. + Raises: AWSBraketRemoteExecutionError """ @@ -129,9 +129,9 @@ def get_braket_device(device: AWSDevice) -> BraketDevice: def get_all_task_ids() -> list[str]: - """Retrieve all the task ids of this account/group from AWS. + """Retrieves all the task ids of this account/group from AWS. - Examples: + Example: >>> get_all_task_ids() ['arn:aws:braket:us-east-1:752542621531:quantum-task/6a46ae9a-d02f-4a23-b46f-eae43471bc22', 'arn:aws:braket:us-east-1:752542621531:quantum-task/11db7e68-2b17-4b00-a4ec-20f662fd4876', @@ -149,10 +149,10 @@ def get_all_task_ids() -> list[str]: def get_all_partial_ids() -> list[str]: - """Retrieve all the task ids of this account/group from AWS and extracts the + """Retrieves all the task ids of this account/group from AWS and extracts the significant part. - Examples: + Example: >>> get_all_partial_ids() ['6a46ae9a-d02f-4a23-b46f-eae43471bc22', '11db7e68-2b17-4b00-a4ec-20f662fd4876', diff --git a/mpqp/execution/connection/env_manager.py b/mpqp/execution/connection/env_manager.py index 5229994f..a6f51537 100644 --- a/mpqp/execution/connection/env_manager.py +++ b/mpqp/execution/connection/env_manager.py @@ -17,7 +17,7 @@ def _create_config_if_needed(): def get_existing_config_str() -> str: """Gets the content of the ``.mpqp`` config file - Examples: + Example: >>> get_existing_config_str() IBM_TOKEN='e7c9*************' IBM_CONFIGURED='True' @@ -35,9 +35,9 @@ def get_existing_config_str() -> str: def load_env_variables() -> bool: - """Load the variables stored in the ``.mpqp`` file. + """Loads the variables stored in the ``.mpqp`` file. - Examples: + Example: >>> os.getenv("IBM_CONFIGURED") >>> load_env_variables() @@ -46,7 +46,7 @@ def load_env_variables() -> bool: 'True' Returns: - True if the variables are loaded correctly. + ``True`` if the variables are loaded correctly. """ load_dotenv(MPQP_CONFIG_PATH, override=True) return True @@ -54,10 +54,10 @@ def load_env_variables() -> bool: @typechecked def get_env_variable(key: str) -> str: - """Load the ``.mpqp`` env file and return the value associated with the key - in parameter. If the variable doesn't exist, an empty string is returned. + """Loads the ``.mpqp`` env file and returns the value associated with the key + in parameter. If the variable does not exist, an empty string is returned. - Examples: + Example: >>> get_env_variable("BRAKET_CONFIGURED") 'True' >>> get_env_variable("RaNdOM") @@ -90,7 +90,7 @@ def save_env_variable(key: str, value: str) -> bool: value: Value to be saved. Returns: - True if the save was successful. + ``True`` if the save was successful. """ _create_config_if_needed() diff --git a/mpqp/execution/connection/ibm_connection.py b/mpqp/execution/connection/ibm_connection.py index 8f873ac7..4e153daa 100644 --- a/mpqp/execution/connection/ibm_connection.py +++ b/mpqp/execution/connection/ibm_connection.py @@ -9,8 +9,7 @@ from termcolor import colored from typeguard import typechecked -from mpqp.execution.connection.env_manager import (get_env_variable, - save_env_variable) +from mpqp.execution.connection.env_manager import get_env_variable, save_env_variable from mpqp.execution.devices import IBMDevice from mpqp.tools.errors import IBMRemoteExecutionError @@ -43,8 +42,7 @@ def config_ibm_account(token: str): def setup_ibm_account(): - """Setup the IBM Q account, by looking at the existing configuration, asking for the token and update - the current account.""" + """Setups and updates the IBM Q account using the existing configuration and by asking for the token .""" was_configured = get_env_variable("IBM_CONFIGURED") == "True" if was_configured: @@ -73,8 +71,10 @@ def setup_ibm_account(): def test_connection() -> bool: - """ - Tests if the connection to the provider works. Returns False if login failed, True otherwise. + """Tests if the connection to the provider works. + + Returns: + ``False`` if login failed. """ try: IBMProvider() @@ -88,10 +88,10 @@ def test_connection() -> bool: def get_IBMProvider() -> IBMProvider: - """ - Returns the IBMProvider needed to get one or several backends for execution + """Returns the IBMProvider needed to get one or several backends for + execution. - Examples: + Example: >>> instance = get_IBMProvider() >>> instance.backends() [, @@ -131,7 +131,7 @@ def get_QiskitRuntimeService() -> QiskitRuntimeService: """ Returns the QiskitRuntimeService needed for remote connection and execution - Examples: + Example: >>> service = get_QiskitRuntimeService() >>> service.jobs() [, @@ -163,9 +163,7 @@ def get_active_account_info() -> str: """ Returns the information concerning the active IBMQ account - Args: - - Examples: + Example: >>> print(get_active_account_info()) Channel: ibm_quantum Instance: ibm-q-startup/colibritd/default @@ -174,7 +172,7 @@ def get_active_account_info() -> str: Verify: True Returns: - a string describin the account info + A string describing the account info. """ provider = get_IBMProvider() account = provider.active_account() @@ -189,19 +187,19 @@ def get_active_account_info() -> str: @typechecked def get_backend(device: IBMDevice) -> BackendV1: """ - Retrieve the IBM Q remote device corresponding to the device in parameter + Retrieves the IBM Q remote device corresponding to the device in parameter Args: device: The IBMDevice to get from IBMQ provider. - Examples: + Example: >>> brisbane = get_backend(IBMDevice.IBM_BRISBANE) >>> brisbane.properties().gates[0].parameters [Nduv(datetime.datetime(2024, 1, 9, 11, 3, 18, tzinfo=tzlocal()), gate_error, , 0.00045619997922344296), Nduv(datetime.datetime(2024, 1, 9, 15, 41, 39, tzinfo=tzlocal()), gate_length, ns, 60)] Returns: - a qiskit.providers.backend.Backend object that will be use to execute circuit + A qiskit.providers.backend.Backend object that will be use to execute circuit. Raises: IBMRemoteExecutionError @@ -231,10 +229,10 @@ def get_backend(device: IBMDevice) -> BackendV1: def get_all_job_ids() -> list[str]: """ - Retrieve all the job ids of this account from the several IBM remote providers + Retrieves all the job ids of this account from the several IBM remote providers (IBMProvider, QiskitRuntimeService, ...) - Examples: + Example: >>> get_all_job_ids() ['cm6pp7e879ps6bbo7m30', 'cm6ou0q70abqioeudkd0', 'cm6opgcpduldih1hq7j0', 'cm01vp4pduldih0uoi2g', 'cnvw8z3b08x0008y3e4g', 'cnvw7qyb08x0008y3e0g', 'cnvw7fdvn4c0008a6ztg', 'cnvw79dvn4c0008a6zt0', diff --git a/mpqp/execution/connection/qlm_connection.py b/mpqp/execution/connection/qlm_connection.py index 21a67dc5..27070e03 100644 --- a/mpqp/execution/connection/qlm_connection.py +++ b/mpqp/execution/connection/qlm_connection.py @@ -19,7 +19,7 @@ @typechecked def config_qlm_account(username: str, password: str, global_config: bool) -> bool: - """Configure and save locally QLM account's information. + """Configures and saves locally QLM account's information. Args: username: QLM username. @@ -79,8 +79,8 @@ def config_qlm_account(username: str, password: str, global_config: bool) -> boo def setup_qlm_account() -> tuple[str, list[Any]]: - """Setup the QLM account, by looking at the existing configuration, asking - for the token and update the current account.""" + """Setups the QLM account, by looking at the existing configuration, asking + for username/password and updating the current account.""" already_configured = get_env_variable("QLM_CONFIGURED") == "True" if already_configured: @@ -110,12 +110,15 @@ def setup_qlm_account() -> tuple[str, list[Any]]: def get_all_job_ids() -> list[str]: - """Retrieve from the remote QLM all the job-ids associated with this account. + """Retrieves from the remote QLM all the job-ids associated with this account. - Examples: + Example: >>> get_all_job_ids() ['Job144361', 'Job144360', 'Job144359', 'Job144358', 'Job144357', 'Job143334', 'Job143333', 'Job143332', 'Job141862', 'Job141861', 'Job141722', 'Job141720', 'Job141716', 'Job141715', 'Job141712', 'Job19341'] + + Returns: + List of all job-ids associated with this account. """ connection = get_QLMaaSConnection() diff --git a/mpqp/execution/devices.py b/mpqp/execution/devices.py index 33b45dae..fe33da1d 100644 --- a/mpqp/execution/devices.py +++ b/mpqp/execution/devices.py @@ -9,17 +9,27 @@ class AvailableDevice(Enum): @abstractmethod def is_remote(self) -> bool: - """Returns True if this device is remote, False if it is local device""" + """Indicates whether a device is remote or not. + + Returns: + ``True`` if this device is remote. + """ pass @abstractmethod def is_gate_based(self) -> bool: - """Returns True if this device is a gate-based simulator/QPU, False otherwise""" + """Indicates whether a device is gate based or not. + + Returns: + ``True`` if this device is a gate-based simulator/QPU.""" pass @abstractmethod def is_simulator(self) -> bool: - """Returns True if this device is a simulator, False if it is a quantum computer, QPU.""" + """Indicates whether a device is a simulator or not. + + Returns: + ``True`` if this device is a simulator.""" pass @@ -153,8 +163,13 @@ def get_arn(self) -> str: region = "us-west-1" elif self == AWSDevice.BRAKET_OQC_LUCY: region = "eu-west-2" - elif self in [AWSDevice.BRAKET_IONQ_HARMONY, AWSDevice.BRAKET_IONQ_ARIA_1, AWSDevice.BRAKET_IONQ_ARIA_2, - AWSDevice.BRAKET_IONQ_FORTE_1, AWSDevice.BRAKET_QUERA_AQUILA]: + elif self in [ + AWSDevice.BRAKET_IONQ_HARMONY, + AWSDevice.BRAKET_IONQ_ARIA_1, + AWSDevice.BRAKET_IONQ_ARIA_2, + AWSDevice.BRAKET_IONQ_FORTE_1, + AWSDevice.BRAKET_QUERA_AQUILA, + ]: region = "us-east-1" else: region = get_env_variable("AWS_DEFAULT_REGION") diff --git a/mpqp/execution/job.py b/mpqp/execution/job.py index 8b9002fa..e06fac9c 100644 --- a/mpqp/execution/job.py +++ b/mpqp/execution/job.py @@ -49,12 +49,12 @@ class JobType(Enum): _settings_ = NoAlias STATE_VECTOR = {BasisMeasure, type(None)} - """Retrieve the vector representing the quantum state, this type is *ideal*.""" + """Retrieves the vector representing the quantum state, this type is *ideal*.""" SAMPLE = {BasisMeasure} - """Measure several times the quantum state in the basis, and retrieve the + """Measures several times the quantum state in the basis, and retrieve the counts. Contrarily to the ``STATE_VECTOR`` job type, this one is *realistic*.""" OBSERVABLE = {ExpectationMeasure} - """Compute the *expectation value* of an observable, using the state_vector + """Computes the *expectation value* of an observable, using the state_vector or the samples. This type is ideal too: it requires some trickery to retrieve the expectation value in an optimal manner.""" @@ -66,9 +66,9 @@ class Job: the submission of a computation/measure of a quantum circuit on a specific hardware. - A job as a type, and a status, and is affected to a specific device. + A job has a type, and a status, and is attached to a specific device. Moreover, the job contains also the quantum circuit and the measure to be - computed on the circuit. + performed on the circuit. Args: job_type: Type of the job (sample, observable, ...). @@ -115,7 +115,7 @@ def __init__( self.id: Optional[str] = None """Contains the id of the remote job, used to retrieve the result from the remote provider. ``None`` if the job is local. If the job is not - local, it will be set latter on.""" + local, it will be set later on.""" @property def status(self): diff --git a/mpqp/execution/providers_execution/atos_execution.py b/mpqp/execution/providers_execution/atos_execution.py index 1162faf0..bcb314d5 100644 --- a/mpqp/execution/providers_execution/atos_execution.py +++ b/mpqp/execution/providers_execution/atos_execution.py @@ -20,6 +20,7 @@ from qat.qlmaas.result import AsyncResult from qat.core.contexts import QPUContext +from qat.core.qpu.qpu import QPUHandler from qat.pylinalg import PyLinalg from qat.clinalg.qpu import CLinalg from qat.core.wrappers.observable import Observable as QLM_Observable @@ -31,7 +32,16 @@ @typechecked -def get_local_qpu(device: ATOSDevice): +def get_local_qpu(device: ATOSDevice) -> QPUHandler: + """ + Returns the myQLM local QPU associated with the ATOSDevice given in parameter. + + Args: + device: ATOSDevice referring to the myQLM local QPU. + + Raises: + ValueError + """ if device.is_remote(): raise ValueError("Excepted a local device, not the remote QLM") if device == ATOSDevice.MYQLM_PYLINALG: @@ -43,7 +53,7 @@ def get_local_qpu(device: ATOSDevice): def generate_state_vector_job( myqlm_circuit: Circuit, device: ATOSDevice = ATOSDevice.MYQLM_PYLINALG ) -> tuple[JobQLM, QPUContext]: - """Generate a myQLM job from the myQLM circuit and select the right myQLM + """Generates a myQLM job from the myQLM circuit and selects the right myQLM QPU to run on it. Args: @@ -68,8 +78,8 @@ def generate_state_vector_job( @typechecked def generate_sample_job(myqlm_circuit: Circuit, job: Job) -> tuple[JobQLM, QPUContext]: - """Generate a myQLM job from the myQLM circuit and job sample info (target, - shots, ...), and select the right myQLM QPU to run on it. + """Generates a myQLM job from the myQLM circuit and job sample info (target, + shots, ...), and selects the right myQLM QPU to run on it. Args: myqlm_circuit: MyQLM circuit of the job. @@ -103,7 +113,7 @@ def generate_sample_job(myqlm_circuit: Circuit, job: Job) -> tuple[JobQLM, QPUCo def generate_observable_job( myqlm_circuit: Circuit, job: Job ) -> tuple[JobQLM, QPUContext]: - """Generate a myQLM job from the myQLM circuit and observable, and select + """Generates a myQLM job from the myQLM circuit and observable, and selects the right myQLM QPU to run on it. Args: @@ -141,18 +151,18 @@ def extract_state_vector_result( job: Optional[Job] = None, device: ATOSDevice = ATOSDevice.MYQLM_PYLINALG, ) -> Result: - """Construct a Result from the result given by the myQLM/QLM run in state + """Constructs a Result from the result given by the myQLM/QLM run in state vector mode. Args: - myqlm_result: Result return by myQLM/QLM after run of the job. + myqlm_result: Result returned by myQLM/QLM after running of the job. job: Original mpqp job used to generate the run. Used to retrieve more easily info to instantiate the result. device: ATOSDevice on which the job was submitted. Used to know if the run was remote or local. Returns: - A Result containing the result info extract from the myQLM/QLM + A Result containing the result info extracted from the myQLM/QLM statevector result. """ if job is None: @@ -181,18 +191,18 @@ def extract_sample_result( job: Optional[Job] = None, device: ATOSDevice = ATOSDevice.MYQLM_PYLINALG, ) -> Result: - """Construct a Result from the result given by the myQLM/QLM run in sample + """Constructs a Result from the result given by the myQLM/QLM run in sample mode. Args: - myqlm_result: Result return by myQLM/QLM after run of the job. + myqlm_result: Result returned by myQLM/QLM after running of the job. job: Original mpqp job used to generate the run. Used to retrieve more easily info to instantiate the result. device: ATOSDevice on which the job was submitted. Used to know if the run was remote or local. Returns: - A Result containing the result info extract from the myQLM/QLM sample + A Result containing the result info extracted from the myQLM/QLM sample result. """ if job is None: @@ -236,18 +246,18 @@ def extract_observable_result( job: Optional[Job] = None, device: ATOSDevice = ATOSDevice.MYQLM_PYLINALG, ) -> Result: - """Construct a Result from the result given by the myQLM/QLM run in + """Constructs a Result from the result given by the myQLM/QLM run in observable mode. Args: - myqlm_result: Result return by myQLM/QLM after run of the job. + myqlm_result: Result returned by myQLM/QLM after running of the job. job: Original mpqp job used to generate the run. Used to retrieve more easily info to instantiate the result. device: ATOSDevice on which the job was submitted. Used to know if the run was remote or local. Returns: - A Result containing the result info extract from the myQLM/QLM + A Result containing the result info extracted from the myQLM/QLM observable result. """ if job is None: @@ -283,17 +293,17 @@ def extract_result( device: ATOSDevice = ATOSDevice.MYQLM_PYLINALG, ) -> Result: """ - Construct a Result from the result given by the myQLM/QLM run. + Constructs a Result from the result given by the myQLM/QLM run. Args: - myqlm_result: Result returned by myQLM/QLM after run of the job. + myqlm_result: Result returned by myQLM/QLM after running of the job. job: Original mpqp job used to generate the run. Used to retrieve more easily info to instantiate the result. device: ATOSDevice on which the job was submitted. Used to know if the run was remote or local. Returns: - A Result containing the result info extract from the myQLM/QLM result. + A Result containing the result info extracted from the myQLM/QLM result. """ if (job is None) or job.device.is_remote(): @@ -317,11 +327,14 @@ def extract_result( @typechecked def job_pre_processing(job: Job) -> Circuit: - """Extract the myQLM circuit and check if ``job.type`` and ``job.measure`` + """Extracts the myQLM circuit and check if ``job.type`` and ``job.measure`` are coherent. Args: job: Mpqp job used to instantiate the myQLM circuit. + + Returns: + The myQLM Circuit translated from the circuit of the job in parameter. """ if ( @@ -342,7 +355,7 @@ def job_pre_processing(job: Job) -> Circuit: @typechecked def run_atos(job: Job) -> Result: - """Execute the job on the right ATOS device precised in the job in parameter. + """Executes the job on the right ATOS device precised in the job in parameter. This function is not meant to be used directly, please use ``runner.run(...)`` instead. @@ -357,7 +370,7 @@ def run_atos(job: Job) -> Result: @typechecked def run_myQLM(job: Job) -> Result: - """Execute the job on the local myQLM simulator. This function is not meant + """Executes the job on the local myQLM simulator. This function is not meant to be used directly, please use ``runner.run(...)`` instead. Args: @@ -405,7 +418,7 @@ def run_myQLM(job: Job) -> Result: @typechecked def submit_QLM(job: Job) -> tuple[str, AsyncResult]: - """Submit the job on the remote QLM machine. This function is not meant to + """Submits the job on the remote QLM machine. This function is not meant to be used directly, please use ``runner.submit(...)`` instead. Args: @@ -453,7 +466,7 @@ def submit_QLM(job: Job) -> tuple[str, AsyncResult]: @typechecked def run_QLM(job: Job) -> Result: - """Submit the job on the remote QLM machine and wait for it to be done. This + """Submits the job on the remote QLM machine and waits for it to be done. This function is not meant to be used directly, please use ``runner.run(...)`` instead. diff --git a/mpqp/execution/providers_execution/aws_execution.py b/mpqp/execution/providers_execution/aws_execution.py index 2bdfbd87..de557865 100644 --- a/mpqp/execution/providers_execution/aws_execution.py +++ b/mpqp/execution/providers_execution/aws_execution.py @@ -21,8 +21,8 @@ @typechecked def run_braket(job: Job) -> Result: """ - Execute the job on the right AWS Braket device (local or remote) precised in the job in parameter and wait until - the task is completed, then return the Result. + Executes the job on the right AWS Braket device (local or remote) precised in the job in parameter and waits until + the task is completed, then returns the Result. This function is not meant to be used directly, please use ``runner.run(...)`` instead. Args: @@ -39,7 +39,7 @@ def run_braket(job: Job) -> Result: @typechecked def submit_job_braket(job: Job) -> tuple[str, QuantumTask]: """ - Submit the job to the right local/remote device and return the generated task. + Submits the job to the right local/remote device and returns the generated task. This function is not meant to be used directly, please use ``runner.submit(...)`` instead. Args: @@ -93,15 +93,15 @@ def submit_job_braket(job: Job) -> tuple[str, QuantumTask]: def extract_result(braket_result: GateModelQuantumTaskResult, job: Optional[Job] = None, device: Optional[AWSDevice] = AWSDevice.BRAKET_LOCAL_SIMULATOR) -> Result: """ - Construct a Result from the result given by the run with Braket. + Constructs a Result from the result given by the run with Braket. Args: - braket_result: Result returned by myQLM/QLM after run of the job. + braket_result: Result returned by myQLM/QLM after running of the job. job: Original mpqp job used to generate the run. Used to retrieve more easily info to instantiate the result. device: AWSDevice on which the job was submitted. Returns: - A Result containing the result info extract from the Braket result. + A Result containing the result info extracted from the Braket result. """ if job is None: if len(braket_result.values) == 0: @@ -148,7 +148,7 @@ def extract_result(braket_result: GateModelQuantumTaskResult, job: Optional[Job] @typechecked def get_result_from_aws_task_arn(task_arn: str = None) -> Result: """ - Retrieve the result, described by the job_id in parameter, from the remote QLM and convert it into an mpqp result. + Retrieves the result, described by the job_id in parameter, from the remote QLM and converts it into an mpqp result. If the job is still running, we wait (blocking) until it is DONE. Args: diff --git a/mpqp/execution/providers_execution/ibm_execution.py b/mpqp/execution/providers_execution/ibm_execution.py index f4c7a4f8..7ade613e 100644 --- a/mpqp/execution/providers_execution/ibm_execution.py +++ b/mpqp/execution/providers_execution/ibm_execution.py @@ -59,7 +59,7 @@ def compute_expectation_value( ibm_circuit: QuantumCircuit, ibm_backend: Optional[BackendV1 | BackendV2], job: Job ) -> Result: """ - Configure observable job and run it locally, and return the corresponding Result. + Configures observable job and run it locally, and returns the corresponding Result. This function is not meant to be used directly, please use ``runner.run(...)`` instead. Args: @@ -97,6 +97,15 @@ def compute_expectation_value( @typechecked def check_job_compatibility(job: Job): + """ + Checks whether the job in parameter has coherent and compatible attributes. + + Args: + job: Job for which we want to check compatibility. + + Raises: + DeviceJobIncompatibleError + """ if not type(job.measure) in job.job_type.value: raise DeviceJobIncompatibleError( f"An {job.job_type.name} job is valid only if the corresponding circuit has an measure in " @@ -130,7 +139,7 @@ def check_job_compatibility(job: Job): @typechecked def run_aer(job: Job): """ - Execute the job on the right AER local simulator precised in the job in parameter. + Executes the job on the right AER local simulator precised in the job in parameter. This function is not meant to be used directly, please use ``runner.run(...)`` instead. Args: @@ -192,7 +201,7 @@ def run_aer(job: Job): @typechecked def submit_ibmq(job: Job) -> tuple[str, RuntimeJob | IBMJob]: """ - Submit the job on the remote IBM device (quantum computer or simulator). + Submits the job on the remote IBM device (quantum computer or simulator). This function is not meant to be used directly, please use ``runner.submit(...)`` instead. Args: @@ -248,7 +257,7 @@ def submit_ibmq(job: Job) -> tuple[str, RuntimeJob | IBMJob]: @typechecked def run_ibmq(job: Job) -> Result: """ - Submit the job on the right IBMQ remote device, precised in the job in parameter, and wait until the job is + Submits the job on the right IBMQ remote device, precised in the job in parameter, and waits until the job is completed. This function is not meant to be used directly, please use ``runner.run(...)`` instead. @@ -273,18 +282,18 @@ def extract_result( ibm_job: Optional[IBMJob | RuntimeJob] = None, ) -> Result: """ - Parse a result from IBM execution (remote or local) into an mpqp Result. - Depending on with which service you run the job (local/remote backend, Estimator, Sampler), + Parses a result from IBM execution (remote or local) into an mpqp Result. + Depending on which service you run the job (local/remote backend, Estimator, Sampler), you retrieve a different result. Args: - result: Result returned by IBM after run of the job. + result: Result returned by IBM after running of the job. job: Original mpqp job used to generate the run. Used to retrieve more easily info to instantiate the result. device: IBMDevice on which the job was submitted. Used to know if the run was remote or local - ibm_job: + ibm_job: IBM or Runtime job used to retrieve info about the circuit and the submitted job (in the remote case). Returns: - A Result containing the result info extract from the IBM result. + A Result containing the result info extracted from the IBM result. """ if job is not None and ( diff --git a/mpqp/execution/result.py b/mpqp/execution/result.py index 45b9a9d4..849d8df9 100644 --- a/mpqp/execution/result.py +++ b/mpqp/execution/result.py @@ -37,9 +37,9 @@ class StateVector: Class representing the state vector of a multi-qubit quantum system. Args: - vector: list of amplitudes defining the state vector - nb_qubits: number of qubits of the state - probabilities: list of probabilities associated to the state vector + vector: List of amplitudes defining the state vector. + nb_qubits: Number of qubits of the state. + probabilities: List of probabilities associated with the state vector. Example: >>> state_vector = StateVector(np.array([1, 1, 1, -1])/2, 2) @@ -393,7 +393,7 @@ class BatchResult: Args: results: List of results. - Examples: + Example: >>> result1 = Result( ... Job(JobType.STATE_VECTOR,QCircuit(0),ATOSDevice.MYQLM_PYLINALG), ... StateVector(np.array([1, 1, 1, -1])/2, 2), diff --git a/mpqp/execution/runner.py b/mpqp/execution/runner.py index 1a1a5c56..cb94be66 100644 --- a/mpqp/execution/runner.py +++ b/mpqp/execution/runner.py @@ -123,7 +123,7 @@ def _run_single( Returns: The Result containing information about the measurement required. - Examples: + Example: >>> c = QCircuit([H(0), CNOT(0, 1), BasisMeasure([0, 1], shots=1000)], label="Bell pair") >>> result = run(c, IBMDevice.AER_SIMULATOR) >>> print(result) @@ -207,9 +207,7 @@ def run( if len(set_device) == 1: return _run_single(circuit, set_device[0], values) - return BatchResult( - [_run_single(circuit, dev, values) for dev in set_device] - ) + return BatchResult([_run_single(circuit, dev, values) for dev in set_device]) return _run_single(circuit, device, values) @@ -224,7 +222,7 @@ def submit( If the circuit depends on variables, the values given in parameters are used to do the substitution. Unlike :meth:`run`, for the moment, one can only submit a circuit to a single device. - Examples: + Example: >>> circuit = QCircuit([H(0), CNOT(0,1), BasisMeasure([0,1], shots=10)]) >>> job_id, job = submit(circuit, ATOSDevice.QLM_LINALG) Logging as user ... diff --git a/mpqp/execution/vqa/vqa.py b/mpqp/execution/vqa/vqa.py index 3d45821e..5e83ff2f 100644 --- a/mpqp/execution/vqa/vqa.py +++ b/mpqp/execution/vqa/vqa.py @@ -42,10 +42,9 @@ def minimize( nb_params: Optional[int] = None, optimizer_options: Optional[dict[str, Any]] = None, ) -> tuple[float, OptimizerInput]: - """This function runs an optimization on the parameters of the circuit, to - minimize the expectation value of the measure of the circuit by it's - observables. Note that this means that the circuit should contain an - expectation measure! + """This function runs an optimization on the parameters of the circuit, in order to + minimize the measured expectation value of observables associated with the given circuit. + Note that this means that the latter should contain an ``ExpectationMeasure``. Examples: >>> alpha, beta = symbols("α β") @@ -88,10 +87,10 @@ def minimize( optimizable: Either the circuit, containing symbols and an expectation measure, or the evaluation function. method: The method used to optimize most of those methods come from - either scipy or cma. If the choice offered in this package are not - covering your needs, you can define your own optimizer. It should be + ``scipy``. If the choices offered in this package are not + covering your needs, you can define your own optimizer. This should be a function taking as input a function representing the circuit, with - as many inputs as the circuit has parameters, as well as optional + as many inputs as the circuit has parameters, and any optional initialization parameters, and returning the optimal value reached and the parameters used to reach this value. device: The device on which the circuit should be run. @@ -105,7 +104,7 @@ def minimize( as is to the minimizer. Returns: - The optimal value reached and the parameters used to reach this value. + The optimal value reached and the parameters corresponding to this value. """ if isinstance(optimizable, QCircuit): if device is None: diff --git a/mpqp/qasm/header_codes/std_lib.qasm b/mpqp/qasm/header_codes/std_lib.qasm index 7ba01263..d45aa606 100644 --- a/mpqp/qasm/header_codes/std_lib.qasm +++ b/mpqp/qasm/header_codes/std_lib.qasm @@ -1 +1 @@ -include 'stdgates.inc'; +include "stdgates.inc"; diff --git a/mpqp/qasm/open_qasm_2_and_3.py b/mpqp/qasm/open_qasm_2_and_3.py index 603cbc0a..34f2c499 100644 --- a/mpqp/qasm/open_qasm_2_and_3.py +++ b/mpqp/qasm/open_qasm_2_and_3.py @@ -87,7 +87,7 @@ def qasm_code(instr: Instr) -> str: we hard include it. Args: - instr: Instr for which we want the corresponding OpenQASM code + instr: Instr for which we want the corresponding OpenQASM code. Returns: OpenQASM definition of ``instr``. @@ -116,7 +116,7 @@ def parse_openqasm_2_file(code: str) -> list[str]: Args: code: The complete OpenQASM 2.0 program, we do not check for correct syntax, it is assumed that the code is - well formed. + well formed. Returns: List of instructions. @@ -168,7 +168,7 @@ def convert_instruction_2_to_3( Args: instr: Instruction to be upgraded. included_instr: Some instructions need new imports, in order to keep - track of which instruction are already + track of which instruction are already. imported in the overall scope, a dictionary of already included instructions is passed and modified along. included_tree_current_node: Current Node in the file inclusion tree. @@ -316,7 +316,7 @@ def open_qasm_2_to_3( temporary bridges between different platforms that use different versions. Args: - code: string containing the OpenQASM 2.0 code and instructions + code: String containing the OpenQASM 2.0 code and instructions. included_tree_current_node: Current Node in the file inclusion tree. path_to_file: Path to the location of the file from which the code is coming (useful for locating imports). @@ -325,7 +325,7 @@ def open_qasm_2_to_3( Returns: Converted OpenQASM code in the 3.0 version. - Examples: + Example: >>> qasm2_str = '''\\ ... OPENQASM 2.0; ... qreg q[2]; @@ -386,7 +386,7 @@ def open_qasm_file_conversion_2_to_3(path: str) -> str: Returns: Converted OpenQASM code in the 3.0 version. - Examples: + Example: >>> example_dir = "example/qasm_files/" >>> with open(example_dir + "main.qasm", "r") as f: ... print(f.read()) @@ -447,7 +447,7 @@ def open_qasm_hard_includes( every instruction in previously included files, directly in the code returned. - Examples: + Example: >>> examples_folder = "tests/qasm/qasm_examples" >>> filename = examples_folder + "/with_include.qasm" >>> with open(filename) as f: @@ -461,7 +461,7 @@ def open_qasm_hard_includes( included_files: The set of files already included, used to avoid duplicate imports and circular dependencies. This set should be initialized with the name of the root file you started with. - path_to_file: Path used to localize files that are inputted. + path_to_file: Path used to localize files that are included. is_openqasm_header_included: Boolean used to only include once the OpenQASM header. @@ -515,7 +515,7 @@ def is_path_in_tree(path: str, any_node: Node): any_node: One node of the tree on which we want to search. Returns: - True if the path is the name of one of the nodes of the tree. + ``True`` if the path is the name of one of the nodes of the tree. """ return any([path in node.name for node in PreOrderIter(any_node.root)]) @@ -530,6 +530,6 @@ def is_path_in_ancestors(path: str, node: Node): node: The node for which we want to search in his ancestors. Returns: - True if the path is the name of one of the ancestors of the node/ + ``True`` if the path is the name of one of the ancestors of the node/ """ return any([path in node.name for node in node.ancestors]) diff --git a/mpqp/qasm/qasm_to_myqlm.py b/mpqp/qasm/qasm_to_myqlm.py index 5770b8ba..9ebd9a12 100644 --- a/mpqp/qasm/qasm_to_myqlm.py +++ b/mpqp/qasm/qasm_to_myqlm.py @@ -9,13 +9,13 @@ @typechecked def qasm2_to_myqlm_Circuit(qasm_str: str) -> Circuit: """ - Converting a OpenQASM 2.0 code into a QLM Circuit + Converting a OpenQASM 2.0 code into a QLM Circuit. Args: - qasm_str: a string representing the OpenQASM 2.0 code + qasm_str: A string representing the OpenQASM 2.0 code. Returns: - a Circuit equivalent to the QASM code in parameter + A Circuit equivalent to the QASM code in parameter. """ parser = OqasmParser(gates={"p": "PH", "u": "U"}) # requires myqlm-interop-1.9.3 circuit = parser.compile(open_qasm_hard_includes(qasm_str, set())) diff --git a/mpqp/tools/choice_tree.py b/mpqp/tools/choice_tree.py index c5243e36..cf4c45a7 100644 --- a/mpqp/tools/choice_tree.py +++ b/mpqp/tools/choice_tree.py @@ -11,7 +11,13 @@ @dataclass class AnswerNode: """Represents a node in a decision tree corresponding to an answer to a question. An answer can lead to an action, - or to another question.""" + or to another question. + + Args: + label: See attribute description. + action: See attribute description. + next_question: See attribute description. + """ label: str """The label or text associated with the answer.""" @@ -23,7 +29,12 @@ class AnswerNode: @dataclass class QuestionNode: - """Represents a node in a decision tree corresponding to a question.""" + """Represents a node in a decision tree corresponding to a question. + + Args: + label: See attribute description. + answers: See attribute description. + """ label: str """The label or text associated with the question.""" answers: list[AnswerNode] diff --git a/mpqp/tools/errors.py b/mpqp/tools/errors.py index 9721e159..f02e276a 100644 --- a/mpqp/tools/errors.py +++ b/mpqp/tools/errors.py @@ -3,7 +3,7 @@ class InstructionParsingError(ValueError): class NumberQubitsError(ValueError): - """Raised when the number of qubits defining in an instruction, a gate or a measurement is not coherent with the + """Raised when the number of qubits defining an instruction, a gate, or a measurement, is not coherent with the related objets (circuit, matrix, observable, etc.)""" @@ -29,4 +29,4 @@ class QLMRemoteExecutionError(RemoteExecutionError): class AWSBraketRemoteExecutionError(RemoteExecutionError): - """Raised when an error occurred during the remote execution process of job(s) on the remote Amazon Braket""" \ No newline at end of file + """Raised when an error occurred during the remote execution process of job(s) on the remote Amazon Braket""" diff --git a/mpqp/tools/generics.py b/mpqp/tools/generics.py index 084f8585..e1f9c9cd 100644 --- a/mpqp/tools/generics.py +++ b/mpqp/tools/generics.py @@ -7,9 +7,9 @@ T = TypeVar("T") ListOrSingle = Union[list[T], T] -"""Type alias for both element of type ``T``, or list of elements of type ``T``.""" +"""Type alias for both elements of type ``T``, or list of elements of type ``T``.""" ArbitraryNestedSequence = Union[Sequence["ArbitraryNestedSequence"], T] -"""Since arbitrarily nested list are defined by recursion, this type allow os to +"""Since arbitrarily nested list are defined by recursion, this type allow us to define a base case. Examples: @@ -43,7 +43,7 @@ def flatten_generator(lst: ArbitraryNestedSequence[T]) -> Iterator[T]: def flatten(lst: ArbitraryNestedSequence[T]) -> list[T]: """Flattens an arbitrarily nested list. - Examples: + Example: >>> nested_list = [[1, 2, [3, 4]], [5, [6, 7]], 8] >>> flatten(nested_list) [1, 2, 3, 4, 5, 6, 7, 8] @@ -68,11 +68,11 @@ def one_lined_repr(obj: object): @typechecked -def find(iterable: Iterable[T], oracle: Callable[[T], bool]): +def find(iterable: Iterable[T], oracle: Callable[[T], bool]) -> T: """ Finds the first element in the iterable that satisfies the given oracle. - Examples: + Example: >>> numbers = [1, 2, 3, 4, 5] >>> is_even = lambda x: x % 2 == 0 >>> find(numbers, is_even) @@ -80,8 +80,8 @@ def find(iterable: Iterable[T], oracle: Callable[[T], bool]): Args: iterable: The iterable to search for the element. - oracle: A callable function that takes an element and returns True if the element satisfies the condition, - False otherwise. + oracle: A callable function that takes an element and returns ``True`` + if the element satisfies the condition. Returns: The first element in the iterable that satisfies the oracle. diff --git a/mpqp/tools/maths.py b/mpqp/tools/maths.py index c3e65957..5fdbd0b8 100644 --- a/mpqp/tools/maths.py +++ b/mpqp/tools/maths.py @@ -17,8 +17,8 @@ @typechecked -def normalize(v: npt.NDArray[np.complex64]): - """Normalize an array representing the amplitudes of the state. +def normalize(v: npt.NDArray[np.complex64]) -> npt.NDArray[np.complex64]: + """Normalizes an array representing the amplitudes of the state. Examples: >>> vector = np.array([1,0,0,1]) @@ -40,9 +40,10 @@ def normalize(v: npt.NDArray[np.complex64]): @typechecked def matrix_eq(lhs: Matrix, rhs: Matrix) -> bool: - r"""Returns True if two matrices lhs and rhs are element-wise equal, within - a tolerance. For respectively each elements `a` and `b` of both inputs, we - check this specific condition: `|a - b| \leq (atol + rtol * |b|)` + r"""Checks whether two matrix are element-wise equal, within a tolerance. + + For respectively each elements `a` and `b` of both inputs, we check this + specific condition: `|a - b| \leq (atol + rtol * |b|)`. Args: lhs: Left-hand side matrix of the equality. @@ -62,7 +63,7 @@ def matrix_eq(lhs: Matrix, rhs: Matrix) -> bool: @typechecked -def is_hermitian(matrix: Matrix): +def is_hermitian(matrix: Matrix) -> bool: """Checks whether the matrix in parameter is hermitian. Args: @@ -90,16 +91,16 @@ def is_hermitian(matrix: Matrix): False Returns: - True if the matrix in parameter is Hermitian + ``True`` if the matrix in parameter is Hermitian. """ return matrix_eq(np.array(matrix).transpose().conjugate(), matrix) # type: ignore @typechecked -def is_unitary(matrix: Matrix): +def is_unitary(matrix: Matrix) -> bool: """Checks whether the matrix in parameter is unitary. - Examples: + Example: >>> a = np.array([[1,1],[1,-1]]) >>> is_unitary(a) False diff --git a/resources/logo.svg b/resources/logo.svg new file mode 100644 index 00000000..f1fc1600 --- /dev/null +++ b/resources/logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/resources/mpqp-usage.gif b/resources/mpqp-usage.gif new file mode 100644 index 00000000..72ac77e9 Binary files /dev/null and b/resources/mpqp-usage.gif differ