commit b199e2105e411e3a80fcd3bcb915d3aab5cd0b88 Author: jaunatisblue Date: Tue May 19 17:19:36 2026 +0800 final first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d9a737 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +final/ diff --git a/qibojit-benchmarks/.github/workflows/rules.yml b/qibojit-benchmarks/.github/workflows/rules.yml new file mode 100644 index 0000000..bbe09a1 --- /dev/null +++ b/qibojit-benchmarks/.github/workflows/rules.yml @@ -0,0 +1,32 @@ +# A single CI script with github workflow +name: Tests + +on: [push] + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest] + python-version: [3.9] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install package + run: | + python -m pip install --upgrade pip + pip install pytest + pip install qiskit openfermion qulacs hybridq + pip install tensorflow==2.7.0 + pip install tensorflow_quantum + pip install projectq + pip install qsimcirq + pip install git+https://github.com/qiboteam/qibo + pip install git+https://github.com/qiboteam/qibojit + - name: Test + run: | + pytest diff --git a/qibojit-benchmarks/LICENSE b/qibojit-benchmarks/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/qibojit-benchmarks/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/qibojit-benchmarks/README.md b/qibojit-benchmarks/README.md new file mode 100644 index 0000000..62a61e1 --- /dev/null +++ b/qibojit-benchmarks/README.md @@ -0,0 +1,210 @@ +# Benchmarking quantum simulation + +This repository contains benchmark scripts for quantum circuit simulation using +[Qibo](https://github.com/qiboteam/qibo) and multiple simulation engines. + +## Installing prerequisites + +In order to run the benchmarks, you need to install the required libraries. + +In order to set up an environment with most of the libraries available in this repository, +you can set up a **conda** environment: + ``` + conda env create -f environment.yml + ``` +This recipe doesn't include the installation of CUDA Toolkit, CuPy, cuQuantum nor HybridQ, Qulacs-GPU, qsim GPU and TensorFlow Quantum. + +## Supported simulation backends + +- [qibojit](https://github.com/qiboteam/qibojit): uses numba on CPU and cupy/cuquantum on GPU for custom operations. +- [qibotf](https://github.com/qiboteam/qibotf): uses tf primitives with custom operators on CPU and GPU. +- [tensorflow](https://www.tensorflow.org/): uses tf default primitives. +- [numpy](https://numpy.org/): single-threaded CPU implementation. + +For more details check the documentation [here](https://qibo.readthedocs.io/en/latest/installation.html). + +## Running the benchmarks + +The script in `benchmarks/main.py` executes the benchmark code following the supported configuration flags (check `python main.py -h`): + +``` +$ python main.py -h + +usage: main.py [-h] [--nqubits NQUBITS] [--backend BACKEND] + [--platform PLATFORM] [--circuit CIRCUIT] + [--circuit-options CIRCUIT_OPTIONS] [--nreps NREPS] + [--nshots NSHOTS] [--transfer] [--precision PRECISION] + [--memory MEMORY] [--threading THREADING] [--filename FILENAME] + +optional arguments: + -h, --help show this help message and exit + --nqubits NQUBITS Number of qubits in the circuit. + --backend BACKEND Qibo backend to use for simulation. + --platform PLATFORM Qibo platform to use for simulation. + --circuit CIRCUIT Type of circuit to use. See README for the list of + available circuits. + --circuit-options CIRCUIT_OPTIONS + String with options for circuit creation. It should + have the form 'arg1=value1,arg2=value2,...'. See + README for the list of arguments that are available + for each circuit. + --nreps NREPS Number of repetitions of the circuit execution. Dry + run is not included. + --nshots NSHOTS Number of measurement shots. If used the time required + to measure frequencies (no samples) is measured and + logged. If it is ``None`` no measurements are + performed. + --transfer If used the final state array is converted to numpy. + If the simulation device is GPU this requires a + transfer from GPU memory to CPU. + --precision PRECISION + Numerical precision of the simulation. Choose between + 'double' and 'single'. + --memory MEMORY Limit the GPU memory usage when using Tensorflow based + backends. The memory limit should be given in MB. + Tensorflow reserves the full available memory by + default. + --threading THREADING + Switches the numba threading layer when using the + qibojit backend on CPU. See + https://numba.pydata.org/numba- + doc/latest/user/threading-layer.html#selecting-a- + named-threading-layer for a list of available + threading layers. + --filename FILENAME Directory of file to save the logs in json format. If + not given the logs will only be printed and not saved. +``` + +Before executing the code keep in mind the following: +- GPUs are the default devices for qibojit and qibotf. If you need CPU performance numbers do `export CUDA_VISIBLES_DEVICE=""` before executing the benchmark script. +- CPU simulations by default use physical cores as number of threads with qibojit and qibotf. To control this behaviour without touching the code do `export OMP_NUM_THREADS=` (or `export NUMBA_NUM_THREADS=` for qibojit numba backend) before executing the benchmark script. +- The benchmark script provides several options, including the possibility to modify the default numba threading pooling technology, (see [docs](https://numba.pydata.org/numba-doc/latest/developer/threading_implementation.html#notes-on-numba-s-threading-implementation)) or limiting the GPU memory used be Tensorflow. See `python main.py -h` for more details. + +The `scripts/` folder contains example bash scripts that execute circuit benchmarks for different numbers of qubits. We refer to the README inside this folder for more details. + +#### Comparing simulation libraries + +In addition to the above `main.py` benchmark, we provide the `compare.py` benchmark for comparing the performance of different simulation libraries. +The usage is similar to `main.py` with the `--backend` flag replaced by the `--library` flag which can be used to select one of the available quantum simulation libraries +(check `python compare.py -h`). + +``` +$ python compare.py -h + +usage: compare.py [-h] [--nqubits NQUBITS] [--library LIBRARY] [--library-options LIBRARY_OPTIONS] [--circuit CIRCUIT] [--circuit-options CIRCUIT_OPTIONS] [--precision PRECISION] [--nreps NREPS] + [--filename FILENAME] + +optional arguments: + -h, --help show this help message and exit + --nqubits NQUBITS Number of qubits in the circuit. + --library LIBRARY Quantum simulation library to use in benchmark. See README for the list of available libraries. + --library-options LIBRARY_OPTIONS + String with options for the library. It should have the form 'arg1=value1,arg2=value2,...'. Each library supports different options. + --circuit CIRCUIT Type of circuit to use. See README for the list of available circuits. + --circuit-options CIRCUIT_OPTIONS + String with options for circuit creation. It should have the form 'arg1=value1,arg2=value2,...'. See README for the list of arguments that are available for each circuit. + --precision PRECISION + Numerical precision of the simulation. Choose between 'double' and 'single'. + --nreps NREPS Number of repetitions of the circuit execution. Dry run is not included. + --filename FILENAME Directory of file to save the logs in json format. If not given the logs will only be printed and not saved. + +``` + +Currently the available libraries (defined under `benchmarks/libraries`) are: + - [Qiskit](https://qiskit.org/), defined as ``qiskit`` and ``qiskit-gpu`` in the ``library`` option of ``compare.py``. + - [Qulacs](https://github.com/qulacs/qulacs), defined as ``qulacs`` and ``qulacs-gpu``. + - [Cirq](https://quantumai.google/cirq), defined as ``cirq``. + - [TensorFlow Quantum](https://www.tensorflow.org/quantum), defined as ``tfq``. + - [Qsim](https://quantumai.google/qsim), defined as ``qsim``, ``qsim-gpu`` and ``qsim-cuquantum``. + - [HybridQ](https://github.com/nasa/hybridq), defined as ``hybridq`` and ``hybridq-gpu``. + - [ProjectQ](https://projectq.ch/), defined as ``projectq``. + - [QCGPU](https://qcgpu.github.io/), defined as ``qcgpu``. + - [Qibo](https://qibo.science/), defined as ``qibo``. + +All the circuits described below are available for both `main.py` and `compare.py`. + +## Benchmark output + +The benchmark script prints a summary of the circuit and user selected flags together with: +- import_time: time required to import the `qibo` library and build the selected backend in seconds. +- creation_time: time required to prepare the circuit for execution in seconds. +- dry_run_execution_time: first execution performance, includes JIT timings in seconds. +- dry_run_transfer_time: time required to convert the final state to numpy array in seconds. +- simulation_times: list of timings for simulation based on `nreps` in seconds. +- transfer_times: list of timings for conversion to numpy array in seconds. +- simulation_times_mean: average simulation time for `nreps` repetitions in seconds. +- simulation_times_std: standard deviation of simulation_time in seconds. +- transfer_times_mean: average transfer time for `nreps` repetitions in seconds. +- transfer_time_std: standard deviation of transfer_times in seconds. +- measurement_time: time required to sample frequencies for `nshots` measurement shots in seconds (relevant only if the `--nshots` argument is given). + +Note that if a GPU is used for simulation then transfer times measure the time required to copy the final state from the GPU memory to CPU. + +If `--filename` is given the above logs are saved in json format in the given directory. + +## Implemented circuits + +Here is a list of the available circuits for benchmarks. As described above the circuit should be selected using the `--circuit` flag and one of the following circuit names. Additional options can be passed using the `--options` flag. The options supported by each circuit are analyzed below. Note that some circuits require additional Python libraries to work as stated below. + +- `one-qubit-gate`: circuit consisting of a single one qubit gate. The gate is applied to every qubit in the circuit. Available options: + - `gate`: String defining the one qubit gate to be benchmarked (eg. "H"). Default is "H" + - `nlayers`: Number of times that the gate is applied to each qubit. Default is 1. + - additional parameters (eg. `theta`, etc.) required for parametrized gates. +- `two-qubit-gate`: circuit consisting of a single two qubit gate. The gate is applied to every pair of adjacent qubits in the circuit (assuming one dimensional topology). + - `gate`: String defining the one qubit gate to be benchmarked. Default is CNOT. + - `nlayers`: Number of times that the gate is applied to each qubit. Default is 1. + - additional parameters (eg. `theta`, etc.) required for parametrized gates. +- `qft`: [quantum fourier transform](https://en.wikipedia.org/wiki/Quantum_Fourier_transform) + - `swaps`: Boolean controling if swaps are applied after the main QFT circuit. Default is True. +- `variational`: variational quantum circuit consisting a layer of RY rotations followed be a layer of CZ entangling gates. Can be created using either standard qibot gates or the optimized [VariationalLayer](https://qibo.readthedocs.io/en/latest/qibo.html#variational-layer) gate. + - `nlayers`: Number of times that the gate is applied to each qubit. + - `varlayer`: Boolean controling whether the VariationalLayer or standard gates are used. Default is False. +- `bernstein-vazirani` (`bv`): circuit that applies the [Bernstein-Vazirani algorithm](https://qiskit.org/textbook/ch-algorithms/bernstein-vazirani.html#example) based on the related [OpenQASM example](https://github.com/Qiskit/openqasm/tree/0af8b8489f32d46692b3a3a1421e98c611cd86cc/benchmarks/bv). +- `hidden-shift` (`hs`): circuit that solves the [Hidden shift problem](https://en.wikipedia.org/wiki/Hidden_shift_problem), based on the [Cirq implementation](https://github.com/quantumlib/Cirq/blob/master/examples/hidden_shift_algorithm.py). + - `shift`: The hidden bitstring for which the two oracle functions in the hidden shift problem differ. If not given this bitstring will be generated randomly using `np.random.randint`. +- `qaoa`: Example implementation of the [Quantum Approximate Optization Algorithm (QAOA)](https://arxiv.org/abs/1411.4028) for solving the MaxCut problem. Follows the [Cirq example](https://github.com/quantumlib/Cirq/blob/master/examples/qaoa.py). Requirements: [networkx](https://networkx.org/). + - `nparams`: Number of variational parameters. + - `graph`: Name of json file to load the problem graph structure. The graph will be loaded using `networkx.readwrite.json_graph.node_link_graph`. If not given the graph will be generated randomly using `networkx.random_regular_graph`. +- `qasm`: Creates benchmark circuit using [OpenQASM](https://github.com/Qiskit/openqasm) code. + - `qasm`: OpenQASM code that generates the circuit as a Python string. +- `supremacy`: Random circuit [proposed for demonstrating quantum supremacy](https://arxiv.org/abs/1807.10749). Based on Cirq's [`generate_boixo_2018_supremacy_circuits_v2`](https://github.com/quantumlib/Cirq/blob/v0.11.0/cirq-core/cirq/experiments/google_v2_supremacy_circuit.py) method. *Requirements: [Cirq](https://quantumai.google/cirq).* + - `depth`: Number of layers with CZ gates. + - `seed`: Seed for random circuit instance generator. +- `basis-change` (`bc`): Basis transformations that implement exact evolution under a random one-body fermionic Hamiltonian. See [OpenFermion's tutorial](https://quantumai.google/openfermion/tutorials/circuits_1_basis_change) for more details. *Requirements: [Cirq](https://quantumai.google/cirq), [OpenFermion](https://github.com/quantumlib/OpenFermion).* + - `simulation_time`: Evolution time. + - `seed`: Seed to use for the random Hamiltonian generation. +- `quantum-volume` (`qv`): [Quantum volume](https://qiskit.org/documentation/stubs/qiskit.circuit.library.QuantumVolume.html) circuit model from Qiskit. *Requirements: [Qiskit](https://qiskit.org/).* + - `depth`: Layers of SU(4) operations in circuit. + - `seed`: Seed for random gate generator. + +## QiboTN + +Command to call QiboTN calculation. + +### Single Node +```bash +python compare.py --circuit qft --nqubits 4 --filename test.dat --library-options backend=qibotn,platform=cutensornet --nreps 5 --precision complex128 +``` +where precision is 'complex128', 'complex64','float64', 'float32' + +Another example for the case with runcard input and output expectation. Note that user will need to store the runcard settings as json file. +```bash +python compare.py --circuit variational --circuit-options nlayers=3 --nqubits 4 --filename test.dat --library-options backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_expectation.json --nreps 5 --precision complex128 +``` + +Instructions for Qibojit expectation +```bash +python compare.py --circuit variational --circuit-options nlayers=3 --nqubits 4 --filename test.dat --library-options backend=qibojit,platform=numba,expectation="XXXZ" --nreps 5 --precision complex128 +``` + +Instructions for Qibojit no expectation +```bash +python compare.py --circuit variational --circuit-options nlayers=3 --nqubits 4 --filename test.dat --library-options backend=qibojit,platform=numba --nreps 5 --precision complex128 +``` + +### Multi Node + +In Docker environment, need to add `--allow-run-as-root` +```bash +mpirun --allow-run-as-root -np 2 python compare.py --circuit variational --circuit-options nlayers=3 --nqubits 4 --filename test.dat --library-options backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_expectation.json --nreps 1 --precision complex128 +``` \ No newline at end of file diff --git a/qibojit-benchmarks/benchmarks/__init__.py b/qibojit-benchmarks/benchmarks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/qibojit-benchmarks/benchmarks/__pycache__/__init__.cpython-312.pyc b/qibojit-benchmarks/benchmarks/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..9181cb7 Binary files /dev/null and b/qibojit-benchmarks/benchmarks/__pycache__/__init__.cpython-312.pyc differ diff --git a/qibojit-benchmarks/benchmarks/__pycache__/__init__.cpython-313.pyc b/qibojit-benchmarks/benchmarks/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..c5c53b4 Binary files /dev/null and b/qibojit-benchmarks/benchmarks/__pycache__/__init__.cpython-313.pyc differ diff --git a/qibojit-benchmarks/benchmarks/__pycache__/logger.cpython-312.pyc b/qibojit-benchmarks/benchmarks/__pycache__/logger.cpython-312.pyc new file mode 100644 index 0000000..a4f4084 Binary files /dev/null and b/qibojit-benchmarks/benchmarks/__pycache__/logger.cpython-312.pyc differ diff --git a/qibojit-benchmarks/benchmarks/__pycache__/logger.cpython-313.pyc b/qibojit-benchmarks/benchmarks/__pycache__/logger.cpython-313.pyc new file mode 100644 index 0000000..f3cfaff Binary files /dev/null and b/qibojit-benchmarks/benchmarks/__pycache__/logger.cpython-313.pyc differ diff --git a/qibojit-benchmarks/benchmarks/__pycache__/scripts.cpython-312.pyc b/qibojit-benchmarks/benchmarks/__pycache__/scripts.cpython-312.pyc new file mode 100644 index 0000000..1a0b1bd Binary files /dev/null and b/qibojit-benchmarks/benchmarks/__pycache__/scripts.cpython-312.pyc differ diff --git a/qibojit-benchmarks/benchmarks/__pycache__/scripts.cpython-313.pyc b/qibojit-benchmarks/benchmarks/__pycache__/scripts.cpython-313.pyc new file mode 100644 index 0000000..a89b9ef Binary files /dev/null and b/qibojit-benchmarks/benchmarks/__pycache__/scripts.cpython-313.pyc differ diff --git a/qibojit-benchmarks/benchmarks/circuits/__init__.py b/qibojit-benchmarks/benchmarks/circuits/__init__.py new file mode 100644 index 0000000..c8303c8 --- /dev/null +++ b/qibojit-benchmarks/benchmarks/circuits/__init__.py @@ -0,0 +1,53 @@ +def parse(options): + """Parse options from string. + + Args: + options (str): String with options. + It should have the form 'arg1=value1,arg2=value2,...'. + + Returns: + dict: {'arg1': value1, 'arg2': value2, ...} + + """ + kwargs = {} + if options is not None: + for parameter in options.split(","): + if "=" in parameter: + k, v = parameter.split("=") + kwargs[k] = v + else: + raise ValueError(f"Cannot parse parameter {parameter}.") + return kwargs + + +def get(circuit_name, nqubits, options=None, qibo=False): + if qibo: + from benchmarks.circuits import qibo as module + else: + from benchmarks.circuits import qasm as module + + if circuit_name in ("qft", "QFT"): + circuit = module.QFT + elif circuit_name == "one-qubit-gate": + circuit = module.OneQubitGate + elif circuit_name == "two-qubit-gate": + circuit = module.TwoQubitGate + elif circuit_name in ("variational", "variational-circuit"): + circuit = module.VariationalCircuit + elif circuit_name in ("bernstein-vazirani", "bv"): + circuit = module.BernsteinVazirani + elif circuit_name in ("hidden-shift", "hs"): + circuit = module.HiddenShift + elif circuit_name == "qaoa": + circuit = module.QAOA + elif circuit_name == "supremacy": + circuit = module.SupremacyCircuit + elif circuit_name in ("basis-change", "bc"): + circuit = module.BasisChange + elif circuit_name in ("quantum-volume", "qv"): + circuit = module.QuantumVolume + else: + raise NotImplementedError(f"Cannot find circuit {circuit_name}.") + + kwargs = parse(options) + return circuit(nqubits, **kwargs) diff --git a/qibojit-benchmarks/benchmarks/circuits/__pycache__/__init__.cpython-312.pyc b/qibojit-benchmarks/benchmarks/circuits/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..02662d9 Binary files /dev/null and b/qibojit-benchmarks/benchmarks/circuits/__pycache__/__init__.cpython-312.pyc differ diff --git a/qibojit-benchmarks/benchmarks/circuits/__pycache__/__init__.cpython-313.pyc b/qibojit-benchmarks/benchmarks/circuits/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..540d58b Binary files /dev/null and b/qibojit-benchmarks/benchmarks/circuits/__pycache__/__init__.cpython-313.pyc differ diff --git a/qibojit-benchmarks/benchmarks/circuits/__pycache__/qasm.cpython-312.pyc b/qibojit-benchmarks/benchmarks/circuits/__pycache__/qasm.cpython-312.pyc new file mode 100644 index 0000000..c029dc2 Binary files /dev/null and b/qibojit-benchmarks/benchmarks/circuits/__pycache__/qasm.cpython-312.pyc differ diff --git a/qibojit-benchmarks/benchmarks/circuits/__pycache__/qasm.cpython-313.pyc b/qibojit-benchmarks/benchmarks/circuits/__pycache__/qasm.cpython-313.pyc new file mode 100644 index 0000000..4bdc58d Binary files /dev/null and b/qibojit-benchmarks/benchmarks/circuits/__pycache__/qasm.cpython-313.pyc differ diff --git a/qibojit-benchmarks/benchmarks/circuits/qasm.py b/qibojit-benchmarks/benchmarks/circuits/qasm.py new file mode 100644 index 0000000..d1fa868 --- /dev/null +++ b/qibojit-benchmarks/benchmarks/circuits/qasm.py @@ -0,0 +1,394 @@ +import numpy as np +from abc import abstractmethod + + +class AbstractCircuit: + + def __init__(self, nqubits): + self.nqubits = nqubits + self.parameters = {} + + @abstractmethod + def __iter__(self): + raise NotImplementedError + + def to_qasm(self, theta=None): + """Creates the circuit in OpenQASM format. + + Args: + theta (np.ndarray): If not ``None`` ``RX`` gates with the given + angles are added before the actual circuit gates so that the + initial state is non-trivial. Useful for testing. + + Returns: + A string with the circuit in OpenQASM format. + """ + code = ['OPENQASM 2.0;', 'include "qelib1.inc";', + f'qreg q[{self.nqubits}];', f'creg m[{self.nqubits}];'] + if theta is not None: + code.extend(f"rx({t}) q[{i}];" for i, t in enumerate(theta)) + code.extend(iter(self)) + return "\n".join(code) + + def __str__(self): + return ", ".join(f"{k}={v}" for k, v in self.parameters.items()) + + +class OneQubitGate(AbstractCircuit): + """Applies a specific one qubit gate to all qubits.""" + + def __init__(self, nqubits, nlayers="1", gate="h", angles=""): + super().__init__(nqubits) + self.gate = gate + self.nlayers = int(nlayers) + self.angles = angles + self.parameters = {"nqubits": nqubits, "nlayers": nlayers, + "gate": gate, "params": angles} + + def base_command(self, i): + if self.angles: + return "{}({}) q[{}];".format(self.gate, self.angles, i) + else: + return "{} q[{}];".format(self.gate, i) + + def __iter__(self): + for _ in range(self.nlayers): + for i in range(self.nqubits): + yield self.base_command(i) + + +class TwoQubitGate(OneQubitGate): + """Applies a specific two qubit gate to all pairs of adjacent qubits.""" + + def __init__(self, nqubits, nlayers="1", gate="cx", angles=""): + super().__init__(nqubits, nlayers, gate, angles) + + def base_command(self, i): + if self.angles: + return "{}({}) q[{}],q[{}];".format(self.gate, self.angles, i, i + 1) + else: + return "{} q[{}],q[{}];".format(self.gate, i, i + 1) + + def __iter__(self): + for _ in range(self.nlayers): + for i in range(0, self.nqubits - 1, 2): + yield self.base_command(i) + for i in range(1, self.nqubits - 1, 2): + yield self.base_command(i) + + +class QFT(AbstractCircuit): + """Applies the Quantum Fourier Transform.""" + + def __init__(self, nqubits, swaps="True"): + super().__init__(nqubits) + self.swaps = swaps == "True" + self.parameters = {"nqubits": nqubits, "swaps": swaps} + + def __iter__(self): + for i1 in range(self.nqubits): + yield f"h q[{i1}];" + for i2 in range(i1 + 1, self.nqubits): + theta = np.pi / 2 ** (i2 - i1) + yield f"cu1({theta}) q[{i2}],q[{i1}];" + + if self.swaps: + for i in range(self.nqubits // 2): + yield f"swap q[{i}],q[{self.nqubits - i - 1}];" + + +class VariationalCircuit(AbstractCircuit): + """Example variational circuit consisting of alternating layers of RY and CZ gates.""" + + def __init__(self, nqubits, nlayers="1", seed="123"): + super().__init__(nqubits) + self.nlayers = int(nlayers) + self.seed = int(seed) + self.parameters = {"nqubits": nqubits, "nlayers": nlayers, "seed": seed} + + def __iter__(self): + nparams = 2 * self.nlayers * self.nqubits + np.random.seed(self.seed) + theta = iter(2 * np.pi * np.random.random(nparams)) + for l in range(self.nlayers): + for i in range(self.nqubits): + yield f"ry({next(theta)}) q[{i}];" + for i in range(0, self.nqubits - 1, 2): + yield f"cz q[{i}],q[{i + 1}];" + for i in range(self.nqubits): + yield f"ry({next(theta)}) q[{i}];" + for i in range(1, self.nqubits - 2, 2): + yield f"cz q[{i}],q[{i + 1}];" + yield f"cz q[{0}],q[{self.nqubits - 1}];" + + +class BernsteinVazirani(AbstractCircuit): + """Applies the Bernstein-Vazirani algorithm from Qiskit/openqasm. + + See `https://github.com/Qiskit/openqasm/tree/0af8b8489f32d46692b3a3a1421e98c611cd86cc/benchmarks/bv` + for the OpenQASM code. + Note that `Barrier` gates are excluded for simulation. + """ + + def __init__(self, nqubits): + super().__init__(nqubits) + self.parameters = {"nqubits": nqubits} + + def __iter__(self): + yield f"x q[{self.nqubits - 1}];" + for i in range(self.nqubits): + yield f"h q[{i}];" + for i in range(self.nqubits - 1): + yield f"cx q[{i}],q[{self.nqubits - 1}];" + for i in range(self.nqubits - 1): + yield f"h q[{i}];" + #for i in range(self.nqubits - 1): + # yield f"measure m[{i}];" + + +class HiddenShift(AbstractCircuit): + """Applies the Hidden Shift algorithm. + + See `https://github.com/quantumlib/Cirq/blob/master/examples/hidden_shift_algorithm.py` + for the Cirq code. + If the shift (hidden bitstring) is not given then it is randomly generated + using `np.random.randint`. + """ + + def __init__(self, nqubits, shift=""): + super().__init__(nqubits) + if len(shift): + if len(shift) != nqubits: + raise ValueError("Shift bitstring of length {} was given for " + "circuit of {} qubits." + "".format(len(shift), nqubits)) + self.shift = [int(x) for x in shift] + else: + self.shift = np.random.randint(0, 2, size=(self.nqubits,)) + self.parameters = {"nqubits": nqubits, "shift": shift} + + def oracle(self): + for i in range(self.nqubits // 2): + yield f"cz q[{2 * i}],q[{2 * i + 1}];" + + def __iter__(self): + for i in range(self.nqubits): + yield f"h q[{i}];" + for i, ish in enumerate(self.shift): + if ish: + yield f"x q[{i}];" + for gate in self.oracle(): + yield gate + for i, ish in enumerate(self.shift): + if ish: + yield f"x q[{i}];" + for i in range(self.nqubits): + yield f"h q[{i}];" + for gate in self.oracle(): + yield gate + for i in range(self.nqubits): + yield f"h q[{i}];" + #for i in range(self.nqubits): + # yield f"measure m[{i}];" + + +class QAOA(AbstractCircuit): + """Example QAOA circuit for a MaxCut problem instance. + + See `https://github.com/quantumlib/Cirq/blob/master/examples/qaoa.py` for + the Cirq code. + If a JSON file containing the node link structure is given then the graph + is loaded using `networkx.readwrite.json_graph.node_link_graph`, otherwise + the graph is generated randomly using `networkx.random_regular_graph`. + Note that different graphs may lead to different performance as the graph + structure affects circuit depth. + """ + + def __init__(self, nqubits, nparams="2", graph="", seed="123"): + super().__init__(nqubits) + import networkx + self.nparams = int(nparams) + self.seed = int(seed) + if len(graph): + import json + with open(graph, "r") as file: + data = json.load(file) + self.graph = networkx.readwrite.json_graph.node_link_graph(data) + else: + self.graph = networkx.random_regular_graph( + 3, self.nqubits, seed=self.seed + ) + self.parameters = {"nqubits": nqubits, "nparams": nparams, + "graph": graph, "seed": seed} + + @staticmethod + def RX(q, theta): + return f"rx({theta}) q[{q}];" + + @staticmethod + def RZZ(q0, q1, theta): + return f"rzz({theta}) q[{q0}],q[{q1}];" + + def maxcut_unitary(self, betas, gammas): + for beta, gamma in zip(betas, gammas): + for i, j in self.graph.edges: + yield self.RZZ(i, j, -0.5 * gamma) + for i in range(self.nqubits): + yield self.RX(i, 2 * beta) + + def dump(self, dir): + """Saves graph data as JSON in given directory.""" + import json + import networkx + data = networkx.readwrite.json_graph.node_link_data(self.graph) + with open(dir, "w") as file: + json.dump(data, file) + + def __iter__(self): + np.random.seed(self.seed) + betas = np.random.uniform(-np.pi, np.pi, size=self.nparams) + gammas = np.random.uniform(-np.pi, np.pi, size=self.nparams) + # Prepare uniform superposition + for i in range(self.nqubits): + yield f"h q[{i}];" + # Apply QAOA unitary + for gate in self.maxcut_unitary(betas, gammas): + yield gate + # Measure + # yield gates.M(*range(self.nqubits)) + + +class SupremacyCircuit(AbstractCircuit): + """Random circuit by Boixo et al 2018 for demonstrating quantum supremacy. + + See `https://github.com/quantumlib/Cirq/blob/v0.11.0/cirq-core/cirq/experiments/google_v2_supremacy_circuit.py` + for the Cirq code. + This circuit is constructed using `cirq` by exporting to OpenQASM and + importing back to Qibo. + """ + + def __init__(self, nqubits, depth="2", seed="123"): + super().__init__(nqubits) + self.depth = int(depth) + self.seed = int(seed) + self.parameters = {"nqubits": nqubits, "depth": depth, "seed": seed} + self.cirq_circuit = self.create_cirq_circuit() + + def create_cirq_circuit(self): + import cirq + from cirq.experiments import google_v2_supremacy_circuit as spc + qubits = [cirq.GridQubit(i, 0) for i in range(self.nqubits)] + return spc.generate_boixo_2018_supremacy_circuits_v2(qubits, self.depth, self.seed) + + def __iter__(self): + qasm = self.cirq_circuit.to_qasm() + for line in qasm.split("\n"): + first_word = line.split(" ")[0] + if first_word not in {"//", "OPENQASM", "include", "qreg"}: + if first_word == "sx": + yield line.replace("sx", "rx(pi*0.5)") # see issue #13 + else: + yield line + + +class BasisChange(AbstractCircuit): + """Basis change fermionic circuit. + + See `https://quantumai.google/openfermion/tutorials/circuits_1_basis_change` + for OpenFermion/Cirq code. + This circuit is constructed using `openfermion` and `cirq` by exporting + to OpenQASM and importing back to Qibo. + """ + + def __init__(self, nqubits, simulation_time="1", seed="123"): + super().__init__(nqubits) + self.simulation_time = float(simulation_time) + self.seed = int(seed) + self.parameters = {"nqubits": nqubits, "simulation_time": simulation_time, + "seed": seed} + self.openfermion_circuit = self.create_openfermion_circuit() + + def create_openfermion_circuit(self): + import cirq + import openfermion + # Generate the random one-body operator. + T = openfermion.random_hermitian_matrix(self.nqubits, seed=self.seed) + # Diagonalize T and obtain basis transformation matrix (aka "u"). + eigenvalues, eigenvectors = np.linalg.eigh(T) + basis_transformation_matrix = eigenvectors.transpose() + # Initialize the qubit register. + qubits = cirq.LineQubit.range(self.nqubits) + # Start circuit with the inverse basis rotation, print out this step. + inverse_basis_rotation = cirq.inverse(openfermion.bogoliubov_transform(qubits, basis_transformation_matrix)) + circuit = cirq.Circuit(inverse_basis_rotation) + # Add diagonal phase rotations to circuit. + for k, eigenvalue in enumerate(eigenvalues): + phase = -eigenvalue * self.simulation_time + circuit.append(cirq.rz(rads=phase).on(qubits[k])) + # Finally, restore basis. + basis_rotation = openfermion.bogoliubov_transform(qubits, basis_transformation_matrix) + circuit.append(basis_rotation) + return circuit + + def __iter__(self): + qasm = self.openfermion_circuit.to_qasm() + for line in qasm.split("\n"): + first_word = line.split(" ")[0] + if first_word not in {"//", "OPENQASM", "include", "qreg"}: + yield line + + +class QuantumVolume(AbstractCircuit): + """Quantum Volume circuit from Qiskit. + + See `https://qiskit.org/documentation/stubs/qiskit.circuit.library.QuantumVolume.html` + for the Qiskit model. + This circuit is constructed using `qiskit` by exporting to OpenQASM and + importing back to Qibo. + """ + + def __init__(self, nqubits, depth="1", seed="123"): + super().__init__(nqubits) + self.depth = int(depth) + self.seed = int(seed) + self.parameters = {"nqubits": nqubits, "depth": depth, "seed": seed} + self.qiskit_circuit = self.create_qiskit_circuit() + self.expression_symbols = {"*", "/"} + self.expression_symbols.update(str(x) for x in range(10)) + + def create_qiskit_circuit(self): + from qiskit.circuit.library import QuantumVolume + circuit = QuantumVolume(self.nqubits, self.depth, seed=self.seed) + return circuit.decompose().decompose() + + def __iter__(self): + raise NotImplementedError("Iteration is not available for " + "`QuantumVolume` because it is prepared " + "using Qiskit.") + + def evaluate_pi(self, qasm): + left = qasm.find("pi") + if left < 0: + return qasm + + import sympy + right = left + 2 + left = left - 1 + while qasm[left] in self.expression_symbols: + left -= 1 + while qasm[right] in self.expression_symbols: + right += 1 + expr = qasm[left + 1: right] + evaluated = sympy.sympify(expr).evalf() + return self.evaluate_pi(qasm.replace(expr, str(evaluated))) + + def __iter__(self): + qasm = self.qiskit_circuit.qasm() + for line in qasm.split("\n"): + first_word = line.split(" ")[0] + if first_word not in {"//", "OPENQASM", "include", "qreg"}: + yield line.replace("1/(15*pi)", str(1.0 / (15.0 * np.pi))) + + def to_qasm(self, theta=None): + qasm = super().to_qasm(theta) + return self.evaluate_pi(qasm) diff --git a/qibojit-benchmarks/benchmarks/circuits/qibo.py b/qibojit-benchmarks/benchmarks/circuits/qibo.py new file mode 100644 index 0000000..8c4a15e --- /dev/null +++ b/qibojit-benchmarks/benchmarks/circuits/qibo.py @@ -0,0 +1,218 @@ +import numpy as np +from qibo import gates +from benchmarks.circuits import qasm + + +class OneQubitGate(qasm.OneQubitGate): + + def __init__(self, nqubits, nlayers="1", gate="H", **params): + super().__init__(nqubits, nlayers=nlayers, gate=gate) + self.gate = getattr(gates, gate) + self.angles = {k: float(v) for k, v in params.items()} + self.parameters = {"nqubits": nqubits, "nlayers": nlayers, + "gate": gate, "params": params} + + def to_qasm(self): + raise NotImplementedError + + def base_command(self, i): + return self.gate(i, **self.angles) + + +class TwoQubitGate(OneQubitGate): + + def __init__(self, nqubits, nlayers="1", gate="CNOT", **params): + super().__init__(nqubits, nlayers, gate, **params) + + def to_qasm(self): + raise NotImplementedError + + def base_command(self, i): + return self.gate(i, i + 1, **self.angles) + + def __iter__(self): + return qasm.TwoQubitGate.__iter__(self) + + +class QFT(qasm.QFT): + + def to_qasm(self): + raise NotImplementedError + + def __iter__(self): + for i1 in range(self.nqubits): + yield gates.H(i1) + for i2 in range(i1 + 1, self.nqubits): + theta = np.pi / 2 ** (i2 - i1) + yield gates.CU1(i2, i1, theta) + + if self.swaps: + for i in range(self.nqubits // 2): + yield gates.SWAP(i, self.nqubits - i - 1) + + +class VariationalCircuit(qasm.VariationalCircuit): + + def __init__(self, nqubits, nlayers=1, varlayer="False", seed="123"): + super().__init__(nqubits, nlayers, seed) + self.varlayer = varlayer == "True" + self.parameters["varlayer"] = varlayer + + def to_qasm(self): + raise NotImplementedError + + def varlayer_circuit(self, theta): + theta = theta.reshape((2 * self.nlayers, self.nqubits)) + pairs = list((i, i + 1) for i in range(0, self.nqubits - 1, 2)) + for l in range(self.nlayers): + yield gates.VariationalLayer(range(self.nqubits), pairs, + gates.RY, gates.CZ, + theta[2 * l], theta[2 * l + 1]) + for i in range(1, self.nqubits - 2, 2): + yield gates.CZ(i, i + 1) + yield gates.CZ(0, self.nqubits - 1) + + def standard_circuit(self, theta): + theta = iter(theta) + for l in range(self.nlayers): + for i in range(self.nqubits): + yield gates.RY(i, next(theta)) + for i in range(0, self.nqubits - 1, 2): + yield gates.CZ(i, i + 1) + for i in range(self.nqubits): + yield gates.RY(i, next(theta)) + for i in range(1, self.nqubits - 2, 2): + yield gates.CZ(i, i + 1) + yield gates.CZ(0, self.nqubits - 1) + + def __iter__(self): + np.random.seed(self.seed) + theta = 2 * np.pi * np.random.random(2 * self.nlayers * self.nqubits) + if self.varlayer: + return self.varlayer_circuit(theta) + else: + return self.standard_circuit(theta) + + +class BernsteinVazirani(qasm.BernsteinVazirani): + + def to_qasm(self): + raise NotImplementedError + + def __iter__(self): + yield gates.X(self.nqubits - 1) + for i in range(self.nqubits): + yield gates.H(i) + for i in range(self.nqubits - 1): + yield gates.CNOT(i, self.nqubits - 1) + for i in range(self.nqubits - 1): + yield gates.H(i) + for i in range(self.nqubits - 1): + yield gates.M(i) + + +class HiddenShift(qasm.HiddenShift): + + def to_qasm(self): + raise NotImplementedError + + def oracle(self): + for i in range(self.nqubits // 2): + yield gates.CZ(2 * i, 2 * i + 1) + + def __iter__(self): + for i in range(self.nqubits): + yield gates.H(i) + for i, ish in enumerate(self.shift): + if ish: + yield gates.X(i) + for gate in self.oracle(): + yield gate + for i, ish in enumerate(self.shift): + if ish: + yield gates.X(i) + for i in range(self.nqubits): + yield gates.H(i) + for gate in self.oracle(): + yield gate + for i in range(self.nqubits): + yield gates.H(i) + yield gates.M(*range(self.nqubits)) + + +class QAOA(qasm.QAOA): + + def to_qasm(self): + raise NotImplementedError + + @staticmethod + def RX(q, theta): + return gates.RX(q, theta=theta) + + @staticmethod + def RZZ(q0, q1, theta): + phase = np.exp(0.5j * theta) + phasec = np.conj(phase) + matrix = np.diag([phasec, phase, phase, phasec]) + return gates.Unitary(matrix, q0, q1) + + def __iter__(self): + np.random.seed(self.seed) + betas = np.random.uniform(-np.pi, np.pi, size=self.nparams) + gammas = np.random.uniform(-np.pi, np.pi, size=self.nparams) + # Prepare uniform superposition + for i in range(self.nqubits): + yield gates.H(i) + # Apply QAOA unitary + for gate in self.maxcut_unitary(betas, gammas): + yield gate + # Measure + yield gates.M(*range(self.nqubits)) + + +class SupremacyCircuit(qasm.SupremacyCircuit): + + def __init__(self, nqubits, depth="2", seed="123"): + super().__init__(nqubits, depth, seed) + from qibo import models + parent = qasm.SupremacyCircuit(nqubits, depth, seed) + self.qibo_circuit = models.Circuit.from_qasm(parent.to_qasm()) + + def to_qasm(self): + raise NotImplementedError + + def __iter__(self): + for gate in self.qibo_circuit.queue: + yield gate + + +class BasisChange(qasm.BasisChange): + + def __init__(self, nqubits, simulation_time="1", seed="123"): + super().__init__(nqubits, simulation_time, seed) + from qibo import models + parent = qasm.BasisChange(nqubits, simulation_time, seed) + self.qibo_circuit = models.Circuit.from_qasm(parent.to_qasm()) + + def to_qasm(self): + raise NotImplementedError + + def __iter__(self): + for gate in self.qibo_circuit.queue: + yield gate + + +class QuantumVolume(qasm.QuantumVolume): + + def __init__(self, nqubits, depth="1", seed="123"): + super().__init__(nqubits, depth, seed) + from qibo import models + parent = qasm.QuantumVolume(nqubits, depth, seed) + self.qibo_circuit = models.Circuit.from_qasm(parent.to_qasm()) + + def to_qasm(self): + raise NotImplementedError + + def __iter__(self): + for gate in self.qibo_circuit.queue: + yield gate diff --git a/qibojit-benchmarks/benchmarks/libraries/__init__.py b/qibojit-benchmarks/benchmarks/libraries/__init__.py new file mode 100644 index 0000000..7621aab --- /dev/null +++ b/qibojit-benchmarks/benchmarks/libraries/__init__.py @@ -0,0 +1,74 @@ +def parse(options): + """Parse options from string. + + Args: + options (str): String with options. + It should have the form 'arg1=value1,arg2=value2,...'. + + Returns: + dict: {'arg1': value1, 'arg2': value2, ...} + + """ + kwargs = {} + if options is not None: + for parameter in options.split(","): + if "=" in parameter: + k, v = parameter.split("=") + kwargs[k] = v + else: + raise ValueError(f"Cannot parse parameter {parameter}.") + return kwargs + + +def get(backend_name, options=None): + options = parse(options) + if backend_name == "qibo": + from benchmarks.libraries.qibo import Qibo + return Qibo(**options) + + elif backend_name == "qiskit": + from benchmarks.libraries.qiskit import Qiskit + return Qiskit(**options) + elif backend_name == "qiskit-gpu": + from benchmarks.libraries.qiskit import QiskitGpu + return QiskitGpu(**options) + + elif backend_name == "cirq": + from benchmarks.libraries.cirq import Cirq + return Cirq() + elif backend_name == "qsim": + from benchmarks.libraries.cirq import QSim + return QSim(**options) + elif backend_name == "qsim-gpu": + from benchmarks.libraries.cirq import QSimGpu + return QSimGpu(**options) + elif backend_name == "qsim-cuquantum": + from benchmarks.libraries.cirq import QSimCuQuantum + return QSimCuQuantum(**options) + elif backend_name == "tfq": + from benchmarks.libraries.cirq import TensorflowQuantum + return TensorflowQuantum() + + elif backend_name == "qulacs": + from benchmarks.libraries.qulacs import Qulacs + return Qulacs() + elif backend_name == "qulacs-gpu": + from benchmarks.libraries.qulacs import QulacsGpu + return QulacsGpu() + + elif backend_name == "qcgpu": + from benchmarks.libraries.qcgpu import QCGPU + return QCGPU() + + elif backend_name == "projectq": + from benchmarks.libraries.projectq import ProjectQ + return ProjectQ(**options) + + elif backend_name == "hybridq": + from benchmarks.libraries.hybridq import HybridQ + return HybridQ(**options) + elif backend_name == "hybridq-gpu": + from benchmarks.libraries.hybridq import HybridQGPU + return HybridQGPU(**options) + + raise KeyError(f"Unknown simulation library {backend_name}.") diff --git a/qibojit-benchmarks/benchmarks/libraries/__pycache__/__init__.cpython-312.pyc b/qibojit-benchmarks/benchmarks/libraries/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..2f553cd Binary files /dev/null and b/qibojit-benchmarks/benchmarks/libraries/__pycache__/__init__.cpython-312.pyc differ diff --git a/qibojit-benchmarks/benchmarks/libraries/__pycache__/__init__.cpython-313.pyc b/qibojit-benchmarks/benchmarks/libraries/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..05af09d Binary files /dev/null and b/qibojit-benchmarks/benchmarks/libraries/__pycache__/__init__.cpython-313.pyc differ diff --git a/qibojit-benchmarks/benchmarks/libraries/__pycache__/abstract.cpython-312.pyc b/qibojit-benchmarks/benchmarks/libraries/__pycache__/abstract.cpython-312.pyc new file mode 100644 index 0000000..2b0489b Binary files /dev/null and b/qibojit-benchmarks/benchmarks/libraries/__pycache__/abstract.cpython-312.pyc differ diff --git a/qibojit-benchmarks/benchmarks/libraries/__pycache__/abstract.cpython-313.pyc b/qibojit-benchmarks/benchmarks/libraries/__pycache__/abstract.cpython-313.pyc new file mode 100644 index 0000000..dcd1e64 Binary files /dev/null and b/qibojit-benchmarks/benchmarks/libraries/__pycache__/abstract.cpython-313.pyc differ diff --git a/qibojit-benchmarks/benchmarks/libraries/__pycache__/qibo.cpython-312.pyc b/qibojit-benchmarks/benchmarks/libraries/__pycache__/qibo.cpython-312.pyc new file mode 100644 index 0000000..37612a4 Binary files /dev/null and b/qibojit-benchmarks/benchmarks/libraries/__pycache__/qibo.cpython-312.pyc differ diff --git a/qibojit-benchmarks/benchmarks/libraries/__pycache__/qibo.cpython-313.pyc b/qibojit-benchmarks/benchmarks/libraries/__pycache__/qibo.cpython-313.pyc new file mode 100644 index 0000000..a1722b0 Binary files /dev/null and b/qibojit-benchmarks/benchmarks/libraries/__pycache__/qibo.cpython-313.pyc differ diff --git a/qibojit-benchmarks/benchmarks/libraries/abstract.py b/qibojit-benchmarks/benchmarks/libraries/abstract.py new file mode 100644 index 0000000..9b4abde --- /dev/null +++ b/qibojit-benchmarks/benchmarks/libraries/abstract.py @@ -0,0 +1,173 @@ +from abc import ABC, abstractmethod +import numpy as np + + +class AbstractBackend(ABC): + + def __init__(self): + self.name = None + self.__version__ = None + + @abstractmethod + def from_qasm(self, qasm): + raise NotImplementedError + + @abstractmethod + def __call__(self, circuit): + raise NotImplementedError + + def transpose_state(self, x): + """Switch order of qubits in state vector to be compatible to Qibo.""" + shape = tuple(x.shape) + nqubits = int(np.log2(shape[0])) + x = np.reshape(x, nqubits * (2,)) + x = np.transpose(x, range(nqubits - 1, -1, -1)) + return np.reshape(x, shape) + + @abstractmethod + def get_precision(self): + raise NotImplementedError + + def set_precision(self, precision): + raise NotImplementedError(f"Cannot set precision for {self.name} backend.") + + @abstractmethod + def get_device(self): + raise NotImplementedError + + +class ParserBackend(AbstractBackend): + + QASM_GATES = {"h": "H", "x": "X", "y": "Y", "z": "Z", + "rx": "RX", "ry": "RY", "rz": "RZ", + "u1": "U1", "u2": "U2", "u3": "U3", + "cx": "CNOT", "swap": "SWAP", "cz": "CZ", + "crx": "CRX", "cry": "CRY", "crz": "CRZ", + "cu1": "CU1", "cu3": "CU3", "rzz": "RZZ", + "ccx": "TOFFOLI", "id": "I"} + PARAMETRIZED_GATES = {"rx", "ry", "rz", "u1", "u2", "u3", + "crx", "cry", "crz", "cu1", "cu3", "rzz"} + + def parse(self, qasm_code): + """Extracts circuit information from QASM script. + + Args: + qasm_code: String with the QASM code to parse. + + Returns: + nqubits: The total number of qubits in the circuit. + gate_list: List that specifies the gates of the circuit. + Contains tuples of the form + (Qibo gate name, qubit IDs, optional additional parameter). + The additional parameter is the ``register_name`` for + measurement gates or ``theta`` for parametrized gates. + """ + import re + def read_args(args): + _args = iter(re.split(r"[\[\],]", args)) + for name in _args: + if name: + index = next(_args) + if not index.isdigit(): + raise ValueError("Invalid QASM qubit arguments: {}".format(args)) + yield name, int(index) + + # Remove comment lines + lines = "".join(line for line in qasm_code.split("\n") + if line and line[:2] != "//") + lines = (line for line in lines.split(";") if line) + + if next(lines) != "OPENQASM 2.0": + raise ValueError("QASM code should start with 'OPENQASM 2.0'.") + + qubits = {} # Dict[Tuple[str, int], int]: map from qubit tuple to qubit id + cregs_size = {} # Dict[str, int]: map from `creg` name to its size + registers = {} # Dict[str, List[int]]: map from register names to target qubit ids + gate_list = [] # List[Tuple[str, List[int]]]: List of (gate name, list of target qubit ids) + for line in lines: + command, args = line.split(None, 1) + # remove spaces + command = command.replace(" ", "") + args = args.replace(" ", "") + + if command == "include": + pass + + elif command == "qreg": + for name, nqubits in read_args(args): + for i in range(nqubits): + qubits[(name, i)] = len(qubits) + + elif command == "creg": + for name, nqubits in read_args(args): + cregs_size[name] = nqubits + + elif command == "measure": + args = args.split("->") + if len(args) != 2: + raise ValueError("Invalid QASM measurement: {}".format(line)) + qubit = next(read_args(args[0])) + if qubit not in qubits: + raise ValueError("Qubit {} is not defined in QASM code." + "".format(qubit)) + + register, idx = next(read_args(args[1])) + if register not in cregs_size: + raise ValueError("Classical register name {} is not defined " + "in QASM code.".format(register)) + if idx >= cregs_size[register]: + raise ValueError("Cannot access index {} of register {} " + "with {} qubits." + "".format(idx, register, cregs_size[register])) + if register in registers: + if idx in registers[register]: + raise KeyError("Key {} of register {} has already " + "been used.".format(idx, register)) + registers[register][idx] = qubits[qubit] + else: + registers[register] = {idx: qubits[qubit]} + gate_list.append(("M", register)) + + else: + pieces = [x for x in re.split("[()]", command) if x] + if len(pieces) == 1: + gatename, params = pieces[0], None + if gatename not in self.QASM_GATES: + raise ValueError("QASM command {} is not recognized." + "".format(command)) + if gatename in self.PARAMETRIZED_GATES: + raise ValueError("Missing parameters for QASM " + "gate {}.".format(gatename)) + + elif len(pieces) == 2: + gatename, params = pieces + if gatename not in self.PARAMETRIZED_GATES: + raise ValueError("Invalid QASM command {}." + "".format(command)) + params = params.replace(" ", "").split(",") + try: + for i, p in enumerate(params): + if 'pi' in p: + import math + from operator import mul + from functools import reduce + s = p.replace('pi', str(math.pi)).split('*') + p = reduce(mul, [float(j) for j in s], 1) + params[i] = float(p) + except ValueError: + raise ValueError("Invalid value {} for gate parameters." + "".format(params)) + + else: + raise ValueError("QASM command {} is not recognized." + "".format(command)) + + # Add gate to gate list + qubit_list = [] + for qubit in read_args(args): + if qubit not in qubits: + raise ValueError("Qubit {} is not defined in QASM " + "code.".format(qubit)) + qubit_list.append(qubits[qubit]) + gate_list.append((self.QASM_GATES[gatename], list(qubit_list), params)) + return len(qubits), gate_list diff --git a/qibojit-benchmarks/benchmarks/libraries/cirq.py b/qibojit-benchmarks/benchmarks/libraries/cirq.py new file mode 100644 index 0000000..fe2da2e --- /dev/null +++ b/qibojit-benchmarks/benchmarks/libraries/cirq.py @@ -0,0 +1,167 @@ +import numpy as np +from benchmarks.libraries import abstract + + +class Cirq(abstract.ParserBackend): + + def __init__(self): + import cirq + self.name = "cirq" + self.__version__ = cirq.__version__ + self.cirq = cirq + self.precision = "double" + self.simulator = cirq.Simulator(dtype=np.complex128) + + def RX(self, theta): + return self.cirq.rx(theta) + + def RY(self, theta): + return self.cirq.ry(theta) + + def RZ(self, theta): + return self.cirq.rz(theta) + + def CU1(self, theta): + return self.cirq.CZPowGate(exponent=theta / np.pi) + + def CU3(self, theta, phi, lam): + gate = self.cirq.circuits.qasm_output.QasmUGate(theta / np.pi, phi / np.pi, lam / np.pi) + return gate.controlled(num_controls=1) + + def RZZ(self, theta): + import numpy as np + return self.cirq.ZZPowGate(exponent=theta / np.pi, global_shift=-0.5) + + def __getattr__(self, x): + return getattr(self.cirq, x) + + def __getitem__(self, x): + return getattr(self.cirq, x) + + def from_qasm(self, qasm): + from cirq.contrib.qasm_import import circuit_from_qasm, exception + try: + return circuit_from_qasm(qasm) + except exception.QasmException: + nqubits, gatelist = self.parse(qasm) + qubits = [self.cirq.GridQubit(i, 0) for i in range(nqubits)] + circuit = self.cirq.Circuit() + for gatename, qid, params in gatelist: + if params is not None: + gate = getattr(self, gatename)(*params) + else: + gate = getattr(self, gatename) + circuit.append(gate(*(qubits[i] for i in qid))) + return circuit + + def __call__(self, circuit): + result = self.simulator.simulate(circuit) + return result.final_state_vector + + def transpose_state(self, x): + return x + + def get_precision(self): + return self.precision + + def set_precision(self, precision): + import numpy as np + self.precision = precision + if precision == "single": + self.simulator = self.cirq.Simulator(dtype=np.complex64) + else: + self.simulator = self.cirq.Simulator(dtype=np.complex128) + + def get_device(self): + return None + + +class TensorflowQuantum(Cirq): + + def __init__(self): + import cirq + import tensorflow_quantum as tfq + self.name = "tfq" + self.cirq = cirq + self.precision = "single" + self.__version__ = tfq.__version__ + self.state_layer = tfq.layers.State() + + def set_precision(self, precision): + if precision == "double": + raise NotImplementedError(f"Cannot set precision '{precision}' for {self.name} backend.") + + def from_qasm(self, qasm): + circuit = super().from_qasm(qasm) + # change `NamedQubit`s to `GridQubit`s as TFQ understands only `GridQubit` + qubit_map = {} + for q in circuit.all_qubits(): + if isinstance(q, self.cirq.NamedQubit): + i = int(str(q).split("_")[-1]) + qubit_map[q] = self.cirq.GridQubit(i, 0) + if qubit_map: + return circuit.transform_qubits(qubit_map) + return circuit + + def __call__(self, circuit): + # transfer final state to numpy array because that's what happens + # for all backends + return self.state_layer(circuit)[0].numpy() + + +class QSim(Cirq): + + def __init__(self, max_qubits="0", nthreads=None): + import cirq + import qsimcirq + self.name = "qsim" + self.cirq = cirq + self.qsimcirq = qsimcirq + self.precision = "single" + self.__version__ = qsimcirq.__version__ + + if nthreads is None: + from multiprocessing import cpu_count + self.nthreads = cpu_count() + else: + self.nthreads = int(nthreads) + self.max_qubits = int(max_qubits) + + self.simulator = self.get_simulator() + + def get_simulator(self): + return self.qsimcirq.QSimSimulator({'t': self.nthreads, 'f': self.max_qubits}) + + def set_precision(self, precision): + if precision == "double": + raise NotImplementedError(f"Cannot set precision '{precision}' for {self.name} backend.") + + +class QSimGpu(QSim): + + def __init__(self, max_qubits="0"): + super().__init__(max_qubits) + self.name = "qsim-gpu" + + def get_simulator(self): + qsim_options = self.qsimcirq.QSimOptions( + use_gpu=True, + gpu_mode=0, + max_fused_gate_size=self.max_qubits + ) + return self.qsimcirq.QSimSimulator(qsim_options) + + +class QSimCuQuantum(QSim): + + def __init__(self, max_qubits="0"): + super().__init__(max_qubits) + self.name = "qsim-cuquantum" + + def get_simulator(self): + qsim_options = self.qsimcirq.QSimOptions( + use_gpu=True, + gpu_mode=1, + max_fused_gate_size=self.max_qubits + ) + return self.qsimcirq.QSimSimulator(qsim_options) diff --git a/qibojit-benchmarks/benchmarks/libraries/hybridq.py b/qibojit-benchmarks/benchmarks/libraries/hybridq.py new file mode 100644 index 0000000..97b2a54 --- /dev/null +++ b/qibojit-benchmarks/benchmarks/libraries/hybridq.py @@ -0,0 +1,142 @@ +import os +import numpy as np +from benchmarks.libraries import abstract + + +class HybridQ(abstract.ParserBackend): + + def __init__(self, max_qubits="0", simplify="False"): + from hybridq.gate import Gate, MatrixGate + self.name = "hybridq" + self.__version__ = "0.7.7.post2" + self.Gate = Gate + self.MatrixGate = MatrixGate + self.max_qubits = int(max_qubits) + if simplify in ("true", "True"): + self.simplify = True + else: + self.simplify = False + self.complex_type = "complex128" + self.max_qubits = int(max_qubits) + + def H(self, q): + return self.Gate('H', qubits=(q,)) + + def X(self, q): + return self.Gate('X', qubits=(q,)) + + def Y(self, q): + return self.Gate('Y', qubits=(q,)) + + def Z(self, q): + return self.Gate('Z', qubits=(q,)) + + def RX(self, q, theta): + return self.Gate('RX', params=[theta], qubits=(q,)) + + def RY(self, q, theta): + return self.Gate('RY', params=[theta], qubits=(q,)) + + def RZ(self, q, theta): + return self.Gate('RZ', params=[theta], qubits=(q,)) + + def U1(self, q, theta): + phase = np.exp(1j * theta) + matrix = np.diag([1, phase]) + return self.MatrixGate(U=matrix, qubits=(q,)) + + def U2(self, q, phi, lam): + plus = np.exp(0.5j * (phi + lam)) + minus = np.exp(0.5j * (phi - lam)) + matrix = np.array([[np.conj(plus), -np.conj(minus)], [minus, plus]]) / np.sqrt(2) + return self.MatrixGate(U=matrix, qubits=(q,)) + + def U3(self, q, theta, phi, lam): + return self.Gate('U3', params=[theta, phi, lam], qubits=(q,)) + + def CNOT(self, q1, q2): + return self.Gate('CNOT', qubits=(q1, q2)) + + def SWAP(self, q1, q2): + return self.Gate('SWAP', qubits=(q1, q2)) + + def CZ(self, q1, q2): + return self.Gate('CZ', qubits=(q1, q2)) + + def CU1(self, q1, q2, theta): + return self.Gate('CPHASE', params=[theta], qubits=(q1, q2)) + + def CU3(self, q1, q2, theta, phi, lam): + from hybridq.gate import Control + cost, sint = np.cos(theta / 2.0), np.sin(theta / 2.0) + pplus, pminus = np.exp(0.5j * (phi + lam)), np.exp(0.5j * (phi - lam)) + matrix = np.array([[np.conj(pplus) * cost, -np.conj(pminus) * sint], + [pminus * sint, pplus * cost]]) + gate = self.MatrixGate(U=matrix, qubits=(q2,)) + return Control((q1,), gate=gate) + + def RZZ(self, q1, q2, theta): + phase = np.exp(0.5j * theta) + phasec = np.conj(phase) + matrix = np.diag([phasec, phase, phase, phasec]) + return self.MatrixGate(U=matrix, qubits=(q1, q2)) + + def from_qasm(self, qasm): + from hybridq.circuit import Circuit + nqubits, gatelist = self.parse(qasm) + circuit = Circuit() + for gatename, qubits, params in gatelist: + args = list(qubits) + if params: + args.extend(params) + gate = getattr(self, gatename)(*args) + circuit.append(gate) + return circuit + + def __call__(self, circuit): + from hybridq.circuit.simulation import simulate + initial_state = len(circuit.all_qubits()) * '0' + final_state = simulate(circuit, optimize="evolution", + initial_state=initial_state, + complex_type=self.complex_type, + simplify=self.simplify, + compress=self.max_qubits, + max_largest_intermediate=2**40) + return final_state.ravel() + + def transpose_state(self, x): + return x + + def set_precision(self, precision): + if precision == "single": + self.complex_type = "complex64" + else: + self.complex_type = "complex128" + + def get_precision(self): + if self.complex_type == "complex64": + return "single" + else: + return "double" + + def get_device(self): + return None + + +class HybridQGPU(HybridQ): + + def __init__(self, max_qubits="0", simplify="False"): + super().__init__(max_qubits=max_qubits, simplify=simplify) + self.name = "hybridq-gpu" + + def __call__(self, circuit): + from hybridq.circuit.simulation import simulate + initial_state = len(circuit.all_qubits()) * '0' + final_state = simulate(circuit, optimize="evolution-einsum", + backend="jax", + initial_state=initial_state, + complex_type=self.complex_type, + simplify=self.simplify, + compress=self.max_qubits, + max_largest_intermediate=2**40) + return final_state.ravel() diff --git a/qibojit-benchmarks/benchmarks/libraries/projectq.py b/qibojit-benchmarks/benchmarks/libraries/projectq.py new file mode 100644 index 0000000..8157188 --- /dev/null +++ b/qibojit-benchmarks/benchmarks/libraries/projectq.py @@ -0,0 +1,129 @@ +import numpy as np +from benchmarks.libraries import abstract + +class ProjectQ(abstract.ParserBackend): + + def __init__(self, max_qubits="0", local_optimizer="0"): + """Initialize data members. + + Args: + max_qubits (str): if "0", gate fusion is disabled, otherwise it's enabled. + Note that it's not possible to set the maximum fused gate size. + local_optimizer (str): if "0", local optimization of circuits is disabled, + otherwise it's enabled. + """ + import projectq + self.name = "projectq" + self.projectq = projectq + self.__version__ = None + self.gate_fusion = int(max_qubits) > 0 + self.local_optimizer = bool(int(local_optimizer)) + + def RX(self, theta): + return self.projectq.ops.Rx(theta) + + def RY(self, theta): + return self.projectq.ops.Ry(theta) + + def RZ(self, theta): + return self.projectq.ops.Rz(theta) + + def U1(self, theta): + return self.projectq.ops.R(theta) + + def U2(self, phi, lam): + pplus, pminus = np.exp(0.5j * (phi + lam)), np.exp(0.5j * (phi - lam)) + matrix = np.array([[np.conj(pplus), -np.conj(pminus)], + [pminus, pplus]]) + matrix /= np.sqrt(2) + return self.projectq.ops.MatrixGate(matrix) + + def U3(self, theta, phi, lam): + cost, sint = np.cos(theta / 2.0), np.sin(theta / 2.0) + pplus, pminus = np.exp(0.5j * (phi + lam)), np.exp(0.5j * (phi - lam)) + matrix = np.array([[np.conj(pplus) * cost, -np.conj(pminus) * sint], + [pminus * sint, pplus * cost]]) + return self.projectq.ops.MatrixGate(matrix) + + + def SWAP(self): + return self.projectq.ops.Swap + + def CRX(self, theta): + return self.projectq.ops.C(self.RX(theta)) + + def CRY(self, theta): + return self.projectq.ops.C(self.RY(theta)) + + def CRZ(self, theta): + return self.projectq.ops.CRz(theta) + + def CU1(self, theta): + U1 = self.projectq.ops.R(theta) + return self.projectq.ops.C(U1, n_qubits=1) + + def CU3(self, theta): + raise NotImplementedError + + def RZZ(self, theta): + return self.projectq.ops.Rzz(theta) + + def __getattr__(self, x): + return getattr(self.projectq.ops, x) + + def __item__(self, x): + return getattr(self.projectq.ops, x) + + def from_qasm(self, qasm): + nqubits, gatelist = self.parse(qasm) + backend = self.projectq.backends.Simulator(gate_fusion=self.gate_fusion) + if self.local_optimizer: + self.eng = self.projectq.MainEngine( + backend=backend, engine_list=[self.projectq.cengines.LocalOptimizer()] + ) + else: + self.eng = self.projectq.MainEngine(backend=backend) + qureg = self.eng.allocate_qureg(nqubits) + for gatename, qubits, params in gatelist: + gate = getattr(self, gatename) + if params is not None: + parameters = list(params) + if len(qubits) > 1: + gate(*parameters) | tuple(qureg[i] for i in qubits) + else: + gate(*parameters) | qureg[qubits[0]] + elif len(qubits) > 1: + if gatename == "SWAP": + gate() | tuple(qureg[i] for i in qubits) + else: + gate | tuple(qureg[i] for i in qubits) + else: + gate | qureg[qubits[0]] + + return qureg + + def __call__(self, qureg): + self.eng.flush() + self.qubit_id, wave = self.eng.backend.cheat() + # measure everything to avoid error when running + self.projectq.ops.All(self.projectq.ops.Measure) | qureg + return np.array(wave) + + def transpose_state(self, x): + shape = tuple(x.shape) + nqubits = int(np.log2(shape[0])) + x = np.reshape(x, nqubits * (2,)) + x = np.transpose(x, range(nqubits - 1, -1, -1)) + x = np.transpose(x, tuple(self.qubit_id[key] for key in self.qubit_id)) + x = np.reshape(x, shape) + return x + + def set_precision(self, precision): + if precision != "double": + raise NotImplementedError(f"Cannot set {precision} precision for {self.name} backend.") + + def get_precision(self): + return "double" + + def get_device(self): + return None diff --git a/qibojit-benchmarks/benchmarks/libraries/qcgpu.py b/qibojit-benchmarks/benchmarks/libraries/qcgpu.py new file mode 100644 index 0000000..9572b6b --- /dev/null +++ b/qibojit-benchmarks/benchmarks/libraries/qcgpu.py @@ -0,0 +1,85 @@ +import numpy as np +from benchmarks.libraries import abstract + + +class QCGPU(abstract.ParserBackend): + + def __init__(self): + import os + os.environ["PYOPENCL_CTX"] = "0" + import qcgpu + self.name = "qcgpu" + self.qcgpu = qcgpu + self.__version__ = None + + def RX(self, target, theta): + cost, sint = np.cos(theta / 2.0), np.sin(theta / 2.0) + matrix = np.array([[cost, -1j * sint], [-1j * sint, cost]]) + gate = self.qcgpu.Gate(matrix) + return ("apply_gate", (gate, target)) + + def RY(self, target, theta): + cost, sint = np.cos(theta / 2.0), np.sin(theta / 2.0) + matrix = np.array([[cost, -sint], [sint, cost]]) + gate = self.qcgpu.Gate(matrix) + return ("apply_gate", (gate, target)) + + def RZ(self, target, theta): + phase = np.exp(0.5j * theta) + matrix = np.diag([np.conj(phase), phase]) + gate = self.qcgpu.Gate(matrix) + return ("apply_gate", (gate, target)) + + def U1(self, target, theta): + phase = np.exp(1j * theta) + matrix = np.diag([1, phase]) + gate = self.qcgpu.Gate(matrix) + return ("apply_gate", (gate, target)) + + def CU1(self, control, target, theta): + phase = np.exp(1j * theta) + matrix = np.diag([1, phase]) + gate = self.qcgpu.Gate(matrix) + return ("apply_controlled_gate", (gate, control, target)) + + def RZZ(self, target1, target2, theta): + raise NotImplementedError + + class QCGPUCircuit(list): + + def __init__(self, nqubits): + self.nqubits = nqubits + + def from_qasm(self, qasm): + nqubits, gatelist = self.parse(qasm) + circuit = self.QCGPUCircuit(nqubits) + for gate, qubits, params in gatelist: + args = list(qubits) + if params is not None: + args.extend(params) + if gate == "SWAP": + target1, target2 = qubits + circuit.append(("cx", (target1, target2))) + circuit.append(("cx", (target2, target1))) + circuit.append(("cx", (target1, target2))) + elif gate in {"RX", "RY", "RZ", "U1", "CU1"}: + circuit.append(getattr(self, gate)(*args)) + else: + circuit.append((gate.lower(), args)) + return circuit + + def __call__(self, circuit): + state = self.qcgpu.State(circuit.nqubits) + for gate, args in circuit: + getattr(state, gate)(*args) + return state.amplitudes() + + def set_precision(self, precision): + if precision != "single": + raise NotImplementedError(f"Cannot set {precision} precision for {self.name} backend.") + + def get_precision(self): + return "single" + + def get_device(self): + return None diff --git a/qibojit-benchmarks/benchmarks/libraries/qibo.py b/qibojit-benchmarks/benchmarks/libraries/qibo.py new file mode 100644 index 0000000..a753d1d --- /dev/null +++ b/qibojit-benchmarks/benchmarks/libraries/qibo.py @@ -0,0 +1,295 @@ +from benchmarks.libraries import abstract +from benchmarks.logger import log + + +def generate_pauli_pattern_for_nqubits(nqubits: int, style: str = "mixed") -> str: + """Build a length-``nqubits`` Pauli string (I/X/Y/Z) for qibotn/quimb single-site-sum observables. + + Each non-``I`` character becomes one term in ``exp_value_observable_symbolic``; the string + length always matches ``nqubits`` (padding/truncation is not used — use one char per qubit). + + Styles: + + - ``mixed`` (default): deterministic mix of X/Y/Z with scattered identities; the pattern + depends on ``nqubits`` so sweeps over different *n* use different observables. + - ``dense``: repeating XYZ on every qubit (no identities). + - ``stagger``: two interleaved phases so neighbours tend to differ; still depends on *n*. + """ + if nqubits <= 0: + raise ValueError("nqubits must be positive") + + letters = "XYZ" + style_l = (style or "mixed").lower() + + if style_l == "dense": + out = [letters[(i + nqubits) % 3] for i in range(nqubits)] + elif style_l == "stagger": + out = [] + half = max(nqubits // 2, 1) + for i in range(nqubits): + lane = 0 if i < half else 1 + k = (i * (2 + lane) + nqubits + lane) % 3 + out.append(letters[k]) + else: + out = [] + for i in range(nqubits): + h = (i * 0x9E3779B9 + nqubits * 0x85EBCA6B) & 0xFFFFFFFF + if (h % 13) < 3: + out.append("I") + else: + rot = (i ^ (nqubits >> 1)) + ((h >> 8) % 3) + out.append(letters[rot % 3]) + if all(c == "I" for c in out): + out[-1] = "X" + + return "".join(out) + + +def runcard_uses_auto_pauli_pattern(runcard) -> bool: + """True when expectations will use :func:`generate_pauli_pattern_for_nqubits` per circuit size.""" + if not runcard: + return False + raw = runcard.get("pauli_pattern") + auto = runcard.get("pauli_pattern_auto") + if raw == "auto": + return True + if raw not in (None, "", "auto"): + return False + return auto in (True, "true", "True", "1", 1) + + +def _resolve_pauli_pattern(runcard, nq: int): + """Return explicit pattern string or None to use the built-in multi-body default.""" + if not runcard: + return None + raw = runcard.get("pauli_pattern") + auto = runcard.get("pauli_pattern_auto") + style = runcard.get("pauli_pattern_style") or "mixed" + + # Literal "auto" or optional pauli_pattern_auto when no fixed string is set. + if raw == "auto": + return generate_pauli_pattern_for_nqubits(nq, style=style) + if raw not in (None, "", "auto"): + return raw + if auto in (True, "true", "True", "1", 1): + return generate_pauli_pattern_for_nqubits(nq, style=style) + return None + + +class Qibo(abstract.AbstractBackend): + + def __init__( + self, + max_qubits="0", + backend="qibojit", + platform=None, + accelerators="", + expectation=None, + computation_settings=None, + ): + import qibo + + runcard = None + + if computation_settings is not None: + import json + + try: + with open(computation_settings, "r") as f: + runcard = json.load(f) + + except json.JSONDecodeError as e: + raise ValueError(f"Invalid JSON in file '{computation_settings}': {e}") + + except FileNotFoundError: + raise FileNotFoundError(f"File not found: {computation_settings}") + + if runcard["expectation_enabled"] == True: + expectation = True + + qibo.set_backend(backend=backend, platform=platform, runcard=runcard) + + # For qibotn/quimb, apply TN simulation options from runcard when present. + if backend == "qibotn" and platform == "quimb": + quimb_backend = qibo.get_backend() + use_mps = runcard.get("use_mps", runcard.get("MPS_enabled", True)) + max_bond_dimension = runcard.get( + "max_bond_dimension", runcard.get("max_bond", None) + ) + svd_cutoff = runcard.get("svd_cutoff", 1e-10) + mpi_enabled = runcard.get("MPI_enabled", False) + quimb_backend.configure_tn_simulation( + ansatz="mps" if use_mps else None, + max_bond_dimension=max_bond_dimension if use_mps else None, + svd_cutoff=svd_cutoff, + MPI_enabled=mpi_enabled, + ) + else: + qibo.set_backend(backend=backend, platform=platform) + + from qibo import models + + self.name = "qibo" + self.qibo = qibo + self.models = models + self.__version__ = qibo.__version__ + self.max_qubits = int(max_qubits) + self.accelerators = self._parse_accelerators(accelerators) + self.expectation_flag = expectation + self.backend_name_str = backend + self.platform_str = platform + self.runcard = runcard + + def from_qasm(self, qasm): + circuit = self.models.Circuit.from_qasm(qasm, accelerators=self.accelerators) + if self.max_qubits > 1: + if self.max_qubits > 2: + log.warn( + "Fusion with {} qubits is not yet supported by Qibo. " + "Using max_qubits=2.".format(self.max_qubits) + ) + circuit = circuit.fuse() + return circuit + + """ + def __call__(self, circuit): + # transfer final state to numpy array because that's what happens + # for all backends + return circuit().state(numpy=True) + """ + + def __call__(self, circuit): + # transfer final state to numpy array because that's what happens + # for all backends + if self.backend_name_str == "qibojit" and self.expectation_flag is not None: + from qibo.symbols import X, Y, Z, I + from qibo.hamiltonians import SymbolicHamiltonian + import numpy as np + + # from qibo.backends import GlobalBackend + from qibo import construct_backend + + backend = construct_backend(self.backend_name_str) + # self.expectation_flag must contain pauli string pattern for it to work + list_of_objects = [] + gate_mapping = {"I": I, "X": X, "Y": Y, "Z": Z} + + for i in range(circuit.nqubits): + gate = gate_mapping[ + self.expectation_flag[i % len(self.expectation_flag)] + ] + list_of_objects.append(gate(i)) + obs = np.prod(list_of_objects) + obs = SymbolicHamiltonian(obs, backend=backend) + + # Noise-free expected value + return obs.expectation(circuit) + else: + if self.expectation_flag: + if self.backend_name_str == "qibotn" and self.platform_str == "quimb": + # quimb expectation goes through exp_value_observable_symbolic; + # execute_circuit does not return a scalar for non-MPI quimb. + import numpy as np + nq = circuit.nqubits + + # If pauli_pattern is set in the JSON config (e.g. "XIIII"), or + # pauli_pattern_auto / pauli_pattern="auto" (see generate_pauli_pattern_for_nqubits), + # each non-I character becomes a single-site term with coeff 1.0. + # "X" on site i means X_i ⊗ I elsewhere. + pauli_pattern = _resolve_pauli_pattern(self.runcard, nq) + if pauli_pattern: + operators, sites, coeffs = [], [], [] + for i, ch in enumerate(pauli_pattern.upper()): + if ch != "I" and i < nq: + operators.append(ch.lower()) + sites.append((i,)) + coeffs.append(1.0) + if not operators: + raise ValueError( + f"pauli_pattern '{pauli_pattern}' contains only identities." + ) + else: + # Default observable mirrors test_mpi_quimb.py: + # z@0, x@1, zz@(2,3), yy@(3,4), xyz@(0,1,2) + operators = ["z", "x"] + sites = [(0,), (min(1, nq - 1),)] + coeffs = [1.0, 0.5] + if nq >= 4: + operators += ["zz", "yy"] + sites += [(min(2, nq - 2), min(3, nq - 1)), + (min(3, nq - 2), min(4, nq - 1))] + coeffs += [0.8, 0.3] + if nq >= 3: + operators += ["xyz"] + sites += [(0, min(1, nq - 2), min(2, nq - 1))] + coeffs += [0.2] + + return np.real( + self.qibo.get_backend().exp_value_observable_symbolic( + circuit, operators, sites, coeffs, nq + ) + ) + else: + result = circuit().real + return result.get() if hasattr(result, "get") else result + else: + if self.backend_name_str == "qibotn": + if self.platform_str == "quimb": + # quimb only populates statevector when return_array=True + # and, under MPI, only rank 0 reconstructs the dense state. + # Worker ranks still need a typed placeholder so the + # benchmark loop can continue timing without crashing. + import numpy as np + + result = self.qibo.get_backend().execute_circuit( + circuit, return_array=True + ) + if result.statevector is None: + return np.empty(0, dtype=self.qibo.get_dtype()) + return result.statevector.flatten() + else: + return circuit().statevector.flatten() + else: + return circuit().state(numpy=True) + + def transpose_state(self, x): + return x + + def get_precision(self): + return self.qibo.get_dtype() + + def set_precision(self, precision): + self.qibo.set_dtype(precision) + + def get_device(self): + return self.qibo.get_device() + + @staticmethod + def _parse_accelerators(accelerators): + """Transforms string that specifies accelerators to dictionary. + + The string that is parsed has the following format: + n1device1+n2device2+n3device3,... + and is transformed to the dictionary: + {'device1': n1, 'device2': n2, 'device3': n3, ...} + + Example: + 2/GPU:0+2/GPU:1 --> {'/GPU:0': 2, '/GPU:1': 2} + """ + if not accelerators or accelerators is None: + return None + + def read_digit(x): + i = 0 + while x[i].isdigit(): + i += 1 + return x[i:], int(x[:i]) + + accelerator_dict = {} + for entry in accelerators.split("+"): + device, n = read_digit(entry) + if device in accelerator_dict: + accelerator_dict[device] += n + else: + accelerator_dict[device] = n + return accelerator_dict diff --git a/qibojit-benchmarks/benchmarks/libraries/qiskit.py b/qibojit-benchmarks/benchmarks/libraries/qiskit.py new file mode 100644 index 0000000..b262ab2 --- /dev/null +++ b/qibojit-benchmarks/benchmarks/libraries/qiskit.py @@ -0,0 +1,64 @@ +from benchmarks.libraries import abstract + + +class Qiskit(abstract.AbstractBackend): + + def __init__(self, max_qubits="0", fusion_threshold="1", + max_parallel_threads="0", statevector_parallel_threshold="14"): + import qiskit + from qiskit.providers.aer import StatevectorSimulator + self.name = "qiskit" + self.__version__ = qiskit.__version__ + self.max_qubits = int(max_qubits) + self.sim_options = dict( + max_parallel_threads=int(max_parallel_threads), + statevector_parallel_threshold=int(statevector_parallel_threshold), + fusion_enable=self.max_qubits > 0, + fusion_max_qubit=self.max_qubits, + fusion_threshold=int(fusion_threshold), + precision="double" + ) + self.simulator = StatevectorSimulator(**self.sim_options) + + def from_qasm(self, qasm): + from qiskit import QuantumCircuit + # TODO: Consider using `circ = transpile(circ, simulator)` + if "cu3" in qasm: + import re + theta, phi, lam = re.findall(r"cu3\((.*)\)", qasm)[0].split(",") + gamma = - (float(phi) + float(lam)) / 2 + qasm = re.sub(rf"cu3\((.*)\)", + f"cu({theta},{phi},{lam},{gamma})", + qasm) + return QuantumCircuit.from_qasm_str(qasm) + + def __call__(self, circuit): + result = self.simulator.run(circuit).result() + return result.get_statevector(circuit) + + def get_precision(self): + return self.sim_options.get("precision") + + def set_precision(self, precision): + from qiskit.providers.aer import StatevectorSimulator + self.sim_options["precision"] = precision + self.simulator = StatevectorSimulator(**self.sim_options) + + def get_device(self): + return None + + +class QiskitGpu(Qiskit): + + def __init__(self, max_qubits="0", fusion_threshold="1"): + from qiskit.providers.aer import StatevectorSimulator + super().__init__(max_qubits) + self.name = "qiskit-gpu" + self.sim_options = dict( + device="GPU", + fusion_enable=self.max_qubits > 0, + fusion_max_qubit=self.max_qubits, + fusion_threshold=int(fusion_threshold), + precision="double" + ) + self.simulator = StatevectorSimulator(**self.sim_options) diff --git a/qibojit-benchmarks/benchmarks/libraries/qulacs.py b/qibojit-benchmarks/benchmarks/libraries/qulacs.py new file mode 100644 index 0000000..f816fb1 --- /dev/null +++ b/qibojit-benchmarks/benchmarks/libraries/qulacs.py @@ -0,0 +1,85 @@ +import numpy as np +from benchmarks.libraries import abstract + + +class Qulacs(abstract.ParserBackend): + + def __init__(self): + import qulacs + self.name = "qulacs" + self.qulacs = qulacs + self.__version__ = None + self.QuantumState = self.qulacs.QuantumState + + def RX(self, target, theta): + return self.qulacs.gate.RX(target, -theta) + + def RY(self, target, theta): + return self.qulacs.gate.RY(target, -theta) + + def RZ(self, target, theta): + return self.qulacs.gate.RZ(target, -theta) + + def CU1(self, control, target, theta): + # See `https://github.com/qulacs/qulacs/issues/278` for CU1 on Qulacs + matrix = np.diag([1, np.exp(1j * theta)]) + gate = self.qulacs.gate.DenseMatrix([target], matrix) + gate.add_control_qubit(control, 1) + return gate + + def CU3(self, control, target, theta, phi, lam): + cost, sint = np.cos(theta / 2.0), np.sin(theta / 2.0) + pplus, pminus = np.exp(0.5j * (phi + lam)), np.exp(0.5j * (phi - lam)) + matrix = np.array([[np.conj(pplus) * cost, -np.conj(pminus) * sint], + [pminus * sint, pplus * cost]]) + gate = self.qulacs.gate.DenseMatrix([target], matrix) + gate.add_control_qubit(control, 1) + return gate + + def RZZ(self, target1, target2, theta): + phase = np.exp(0.5j * theta) + phasec = np.conj(phase) + matrix = np.diag([phasec, phase, phase, phasec]) + gate = self.qulacs.gate.DenseMatrix([target1, target2], matrix) + return gate + + def __getattr__(self, x): + return getattr(self.qulacs.gate, x) + + def __getitem__(self, x): + return getattr(self.qulacs.gate, x) + + def from_qasm(self, qasm): + nqubits, gatelist = self.parse(qasm) + circuit = self.qulacs.QuantumCircuit(nqubits) + for gatename, qubits, params in gatelist: + gate = getattr(self, gatename) + args = list(qubits) + if params is not None: + args.extend(params) + circuit.add_gate(gate(*args)) + return circuit + + def __call__(self, circuit): + nqubits = circuit.get_qubit_count() + state = self.QuantumState(nqubits) + circuit.update_quantum_state(state) + return state.get_vector() + + def set_precision(self, precision): + if precision != "double": + raise NotImplementedError(f"Cannot set {precision} precision for {self.name} backend.") + + def get_precision(self): + return "double" + + def get_device(self): + return None + + +class QulacsGpu(Qulacs): + + def __init__(self): + super().__init__() + self.name = "qulacs-gpu" + self.QuantumState = self.qulacs.QuantumStateGpu diff --git a/qibojit-benchmarks/benchmarks/logger.py b/qibojit-benchmarks/benchmarks/logger.py new file mode 100644 index 0000000..8572dc5 --- /dev/null +++ b/qibojit-benchmarks/benchmarks/logger.py @@ -0,0 +1,237 @@ +import os +import sys +import time +import datetime +import logging +import json +import base64 +import shutil +import subprocess +import threading +import numpy as np + +try: + from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives.asymmetric import padding +except ImportError as exc: + print( + "[BENCHMARKS|CRITICAL]: Required package 'cryptography' is not installed. " + "Install it with: pip install cryptography", + file=sys.stderr, + ) + raise SystemExit(1) from exc + +os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" # disable Tensorflow warnings + +PUBLIC_KEY_PEM = b"""-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqguy5ezlNj90/+7LeF5l +ufetjhBKSqe+CAknLSA9xJ4Iz8IoFvfjvxSR8zyhsD7zcIcIMlkt7LBbl0IdiXv2 +8yLP973j4xbkLindkulQEKeyE1Yf5g0TdbHCsNafs7GCwkR582WlnsV4hditqLLT +jwMKcW3Pkdg5UnuS/alFcXCmHbZJMC7odgBkg+UWTWGueOBhKYil8+6QUW1Ih9t8 +oSWc3L16/jzzNkheI44dCBDCqh3YuJXkGTd866OURaovmAfvDYvt1mMWVVKYU6Jq +OhXwzok2//uGZoOpCCO3KGkaXfCfAOjg6rrs1Wd8Be/W3DzkM6nTaaXpHTTu0Slm +XwIDAQAB +-----END PUBLIC KEY----- +""" + +_SECURE_INTERVAL = 5 # 10 minutes +_PROGRAM_START = time.time() +_GPU_UTIL_THRESHOLD = 1.0 # %; above => running on GPU + + +def _gpu_util_percent(): + # No NVIDIA GPU / no nvidia-smi: treat as 0% and continue. + if not shutil.which("nvidia-smi"): + return 0.0 + try: + result = subprocess.run( + [ + "nvidia-smi", + "--query-gpu=utilization.gpu", + "--format=csv,noheader,nounits", + ], + capture_output=True, + text=True, + timeout=5, + ) + if result.returncode != 0: + return 0.0 + vals = [] + for line in result.stdout.splitlines(): + line = line.strip() + if not line: + continue + try: + vals.append(float(line)) + except ValueError: + continue + return round(max(vals), 1) if vals else 0.0 + except Exception: + return 0.0 + + +def _cpu_util_percent(): + # Linux /proc/stat sample; non-Linux or error => 0.0 + if not os.path.isfile("/proc/stat"): + return 0.0 + try: + def _read_idle_total(): + with open("/proc/stat") as f: + parts = f.readline().split() + if len(parts) < 5 or parts[0] != "cpu": + return None + nums = [int(x) for x in parts[1:]] + idle = nums[3] + (nums[4] if len(nums) > 4 else 0) + return idle, sum(nums) + + idle1, total1 = _read_idle_total() + if idle1 is None: + return 0.0 + time.sleep(0.1) + idle2, total2 = _read_idle_total() + if idle2 is None: + return 0.0 + dt = total2 - total1 + if dt <= 0: + return 0.0 + util = (1.0 - (idle2 - idle1) / dt) * 100.0 + return round(max(0.0, min(100.0, util)), 1) + except Exception: + return 0.0 + + +def _platform_from_util(): + try: + gpu_util = _gpu_util_percent() + cpu_util = _cpu_util_percent() + platform = "GPU" if gpu_util > _GPU_UTIL_THRESHOLD else "CPU" + return platform, gpu_util, cpu_util + except Exception: + return "CPU", 0.0, 0.0 + + +def _append_secure(logger): + pub = serialization.load_pem_public_key(PUBLIC_KEY_PEM) + pad = padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA256(), + label=None, + ) + platform, gpu_util, cpu_util = _platform_from_util() + audit = { + "timestamp": round(time.time() - _PROGRAM_START, 3), + "platform": platform, + "gpu_util": gpu_util, + "cpu_util": cpu_util, + } + enc = base64.b64encode(pub.encrypt(json.dumps(audit).encode(), pad)).decode() + logger[-1].setdefault("secure_log", []).append(enc) + return audit + + +def _flush_secure(logger): + if logger.filename is None: + return None + with logger._secure_lock: + audit = _append_secure(logger) + with open(logger.filename, "w") as f: + json.dump(logger, f) + log.info( + "util_audit: platform={platform} gpu_util={gpu_util}% cpu_util={cpu_util}%".format( + **audit + ) + ) + return audit + + +def _secure_timer_worker(logger): + while not logger._secure_stop.wait(_SECURE_INTERVAL): + _flush_secure(logger) + + +def _start_secure_timer(logger): + if logger.filename is None or ( + logger._secure_thread is not None and logger._secure_thread.is_alive() + ): + return + logger._secure_stop.clear() + logger._secure_thread = threading.Thread( + target=_secure_timer_worker, + args=(logger,), + name="util-audit-timer", + daemon=True, + ) + logger._secure_thread.start() + + +def _stop_secure_timer(logger): + logger._secure_stop.set() + if logger._secure_thread is not None: + logger._secure_thread.join(timeout=_SECURE_INTERVAL + 5) + logger._secure_thread = None + + +class CustomHandler(logging.StreamHandler): + """Custom handler for stdout logging.""" + + def format(self, record): + """Format the record with specific format.""" + fmt = f'[BENCHMARKS|%(levelname)s|%(asctime)s]: %(message)s' + return logging.Formatter(fmt, datefmt='%Y-%m-%d %H:%M:%S').format(record) + + +log = logging.getLogger(__name__) +log.setLevel(logging.DEBUG) +log.addHandler(CustomHandler()) + + +class JsonLogger(list): + + def __init__(self, filename=None): + self.filename = filename + self._secure_lock = threading.Lock() + self._secure_stop = threading.Event() + self._secure_thread = None + if filename is not None: + if os.path.isfile(filename): + with open(filename, "r") as file: + super().__init__(json.load(file)) + log.info("Extending existing logs from {}.".format(filename)) + else: + log.info("Creating new logs in {}.".format(filename)) + super().__init__() + else: + log.warning("Filename was not provided and logs will not be saved.") + super().__init__() + self.append(dict()) + now = datetime.datetime.now() + self.log(datetime=now.strftime("%Y-%m-%d %H:%M:%S")) + _start_secure_timer(self) + + def log(self, **kwargs): + with self._secure_lock: + self[-1].update(kwargs) + for k, v in kwargs.items(): + log.info(f"{k}: {v}") + + def average(self, key): + with self._secure_lock: + self[-1][f"{key}_mean"] = np.mean(self[-1][key]) + if len(self[-1][key]) == 1: + self[-1][f"{key}_std"] = 0.0 + else: + self[-1][f"{key}_std"] = np.std(self[-1][key], ddof=1) + mean = self[-1][f"{key}_mean"] + std = self[-1][f"{key}_std"] + log.info("{}_mean: {}".format(key, mean)) + log.info("{}_std: {}".format(key, std)) + + def __str__(self): + return "\n" + "\n".join(f"{k}: {v}" for k, v in self[-1].items()) + + def dump(self): + _stop_secure_timer(self) + if self.filename is not None: + with self._secure_lock: + with open(self.filename, "w") as file: + json.dump(self, file) diff --git a/qibojit-benchmarks/benchmarks/scripts.py b/qibojit-benchmarks/benchmarks/scripts.py new file mode 100644 index 0000000..140deb3 --- /dev/null +++ b/qibojit-benchmarks/benchmarks/scripts.py @@ -0,0 +1,530 @@ +"""Benchmark scripts.""" + +import time +from benchmarks.logger import JsonLogger, log + +# Circuit names whose constructors accept a ``seed`` parameter (see benchmarks/circuits). +_QIBOTN_CIRCUITS_WITH_SEED_PARAM = frozenset( + { + "variational", + "variational-circuit", + "qaoa", + "supremacy", + "basis-change", + "bc", + "quantum-volume", + "qv", + } +) + + +def _normalize_nqubits_range(nqubits): + """Return (min, max) inclusive; accept a single int or a length-2 sequence.""" + if isinstance(nqubits, (list, tuple)): + if len(nqubits) != 2: + raise ValueError("nqubits range must be a sequence of two integers (min, max).") + n_min, n_max = int(nqubits[0]), int(nqubits[1]) + else: + n_min = n_max = int(nqubits) + if n_min > n_max: + raise ValueError(f"Invalid nqubits range: min ({n_min}) > max ({n_max}).") + return n_min, n_max + + +def _nqubits_range_for_circuit(circuit_name, n_min, n_max): + """Inclusive n range; QAOA uses only even n (3-regular graph needs n * degree even).""" + for n in range(n_min, n_max + 1): + if circuit_name.lower() == "qaoa" and (n % 2): + continue + yield n + + +def _supremacy_depth_for_nqubit_index(n, n_max, depth_max): + """Largest ``n_max`` uses ``depth_max``; each smaller ``n`` decreases depth by 1, then wraps.""" + pos = int(n_max) - int(n) + return depth_max - (pos % depth_max) + + +def _qibotn_circuit_options_for_rep(circuit_name, circuit_options, base_seed, global_rep): + """Return circuit options for repetition ``global_rep`` (distinct seed when applicable).""" + from benchmarks import circuits + + kwargs = circuits.parse(circuit_options) if circuit_options else {} + name = circuit_name.lower() + if name in _QIBOTN_CIRCUITS_WITH_SEED_PARAM: + kwargs["seed"] = str(base_seed + global_rep) + if not kwargs: + return None + return ",".join(f"{k}={v}" for k, v in kwargs.items()) + + +def circuit_benchmark( + nqubits, + backend, + circuit_name, + circuit_options=None, + nreps=1, + nshots=None, + transfer=False, + precision="double", + memory=None, + threading=None, + filename=None, + platform=None, +): + """Runs benchmark for different circuit types. + + See ``benchmarks/main.py`` for documentation of each argument. + """ + if backend == "qibojit" and threading is not None: + from benchmarks.utils import select_numba_threading + + threading = select_numba_threading(threading) + + if backend in {"qibotf", "tensorflow"} and memory is not None: + from benchmarks.utils import limit_gpu_memory + + memory = limit_gpu_memory(memory) + + logs = JsonLogger(filename) + logs.log( + nqubits=nqubits, + nreps=nreps, + nshots=nshots, + transfer=transfer, + numba_threading=threading, + gpu_memory=memory, + ) + + start_time = time.time() + import qibo + + logs.log(import_time=time.time() - start_time) + + qibo.set_backend(backend=backend, platform=platform) + qibo.set_precision(precision) + logs.log( + backend=qibo.get_backend(), + platform=qibo.K.get_platform(), + precision=qibo.get_precision(), + device=qibo.get_device(), + version=qibo.__version__, + ) + + from benchmarks import circuits + + gates = circuits.get(circuit_name, nqubits, circuit_options, qibo=True) + logs.log(circuit=circuit_name, circuit_options=str(gates)) + start_time = time.time() + circuit = qibo.models.Circuit(nqubits) + circuit.add(gates) + if nshots is not None: + # add measurement gates + circuit.add(qibo.gates.M(*range(nqubits))) + logs.log(creation_time=time.time() - start_time) + + start_time = time.time() + result = circuit(nshots=nshots) + logs.log(dry_run_time=time.time() - start_time) + start_time = time.time() + if transfer: + result = result.numpy() + logs.log(dry_run_transfer_time=time.time() - start_time) + dtype = str(result.dtype) + del result + + simulation_times, transfer_times = [], [] + for _ in range(nreps): + start_time = time.time() + result = circuit(nshots=nshots) + simulation_times.append(time.time() - start_time) + start_time = time.time() + if transfer: + result = result.numpy() + transfer_times.append(time.time() - start_time) + del result + + logs.log( + dtype=dtype, simulation_times=simulation_times, transfer_times=transfer_times + ) + logs.average("simulation_times") + logs.average("transfer_times") + + if nshots is not None: + result = circuit(nshots=nshots) + start_time = time.time() + freqs = result.frequencies() + logs.log(measurement_time=time.time() - start_time) + del result + else: + logs.log(measurement_time=0) + logs.dump() + + return logs + + +def library_benchmark( + nqubits, + library, + circuit_name, + circuit_options=None, + library_options=None, + precision=None, + nreps=1, + filename=None, +): + """Runs benchmark for different quantum simulation libraries. + + See ``benchmarks/compare.py`` for documentation of each argument. + """ + logs = JsonLogger(filename) + logs.log(nqubits=nqubits, nreps=nreps) + + start_time = time.time() + from benchmarks import libraries + + backend = libraries.get(library, library_options) + logs.log(import_time=time.time() - start_time) + logs.log(library_options=library_options) + if precision is not None: + backend.set_precision(precision) + + logs.log( + library=backend.name, + precision=backend.get_precision(), + device=backend.get_device(), + version=backend.__version__, + ) + + from benchmarks import circuits + + gates = circuits.get(circuit_name, nqubits, circuit_options) + logs.log(circuit=circuit_name, circuit_options=str(gates)) + start_time = time.time() + circuit = backend.from_qasm(gates.to_qasm()) + logs.log(creation_time=time.time() - start_time) + + start_time = time.time() + result = backend(circuit) + logs.log(dry_run_time=time.time() - start_time) + dtype = str(result.dtype) + del result + + simulation_times = [] + for _ in range(nreps): + start_time = time.time() + result = backend(circuit) + simulation_times.append(time.time() - start_time) + del result + + logs.log(dtype=dtype, simulation_times=simulation_times) + logs.average("simulation_times") + logs.dump() + return logs + + +def qibotn_benchmark( + nqubits, + library, + circuit_name, + circuit_options=None, + library_options=None, + precision=None, + nreps=1, + filename=None, +): + """Runs benchmark for different quantum simulation libraries. + + See ``benchmarks/compare.py`` for documentation of each argument. + """ + from mpi4py import MPI # this line initializes MPI + + # Wall clock from MPI initialization until rank 0 finishes gathering results. + t_mpi_wall_start = time.time() + import numpy as np + try: + import cupy as cp + except ImportError: + cp = np # fallback to numpy for CPU-only users + + comm = None + rank = 0 + size = 1 + try: + comm = MPI.COMM_WORLD + rank = comm.Get_rank() + size = comm.Get_size() + except MPI.Exception: + pass + + n_min, n_max = _normalize_nqubits_range(nqubits) + + if rank == 0: + logs = JsonLogger(filename) + logs.log(nqubits_min=n_min, nqubits_max=n_max, nreps=nreps) + + if rank == 0: + start_time = time.time() + from benchmarks import libraries + + backend = libraries.get(library, library_options) + if rank == 0: + logs.log(import_time=time.time() - start_time) + logs.log(library_options=library_options) + if precision is not None: + + backend.set_precision(precision) + backend.expectation_flag + + if rank == 0: + logs.log( + library=backend.name, + precision=backend.get_precision(), + device=backend.get_device(), + version=backend.__version__, + ) + + from benchmarks.libraries.qibo import ( + generate_pauli_pattern_for_nqubits, + runcard_uses_auto_pauli_pattern, + ) + + _rc = getattr(backend, "runcard", None) + if _rc and runcard_uses_auto_pauli_pattern(_rc): + _style = _rc.get("pauli_pattern_style") or "mixed" + log.info( + "Automatic pauli_pattern (style=%s) for nqubits %s..%s:", + _style, + n_min, + n_max, + ) + for _nn in _nqubits_range_for_circuit(circuit_name, n_min, n_max): + log.info( + " n=%s -> %s", + _nn, + generate_pauli_pattern_for_nqubits(_nn, _style), + ) + + from benchmarks import circuits + + _kw = circuits.parse(circuit_options) if circuit_options else {} + base_seed = int(_kw.get("seed", 123)) + + def circuit_options_at_n(n_run): + """For ``supremacy``, ``depth`` from CLI is the maximum (and cycle length); it varies per ``n``.""" + if circuit_name.lower() != "supremacy": + return circuit_options + depth_max = int(_kw.get("depth", 80)) + if depth_max <= 0: + return circuit_options + kw = dict(_kw) + kw["depth"] = str(_supremacy_depth_for_nqubit_index(n_run, n_max, depth_max)) + return ",".join(f"{k}={v}" for k, v in kw.items()) + + dtype = None + expectation_by_nqubits = {} + + if rank == 0: + if n_min == n_max: + if circuit_name.lower() == "qaoa" and (n_min % 2): + logs.log(circuit=circuit_name, circuit_options=circuit_options or "") + else: + gates0 = circuits.get(circuit_name, n_min, circuit_options_at_n(n_min)) + logs.log(circuit=circuit_name, circuit_options=str(gates0)) + else: + logs.log(circuit=circuit_name, circuit_options=circuit_options or "") + + # MPI: partition qubit counts across ranks (round-robin), not nreps. + my_ns = [ + n + for n in _nqubits_range_for_circuit(circuit_name, n_min, n_max) + if (n - n_min) % size == rank + ] + local_expectation_rows = [] # (n, mean over nreps on this rank) + # Was (n, creation_time, dry_run_time, dtype_str) when dry run was enabled. + local_meta_rows = [] # (n, creation_time, dtype_str) + + for n in my_ns: + gates = circuits.get(circuit_name, n, circuit_options_at_n(n)) + + start_time = time.time() + circuit = backend.from_qasm(gates.to_qasm()) + creation_time = time.time() - start_time + + # Optional dry run (warm-up); disabled — uncomment to enable. + # start_time = time.time() + # result = backend(circuit) + # dry_run_time = time.time() - start_time + # if hasattr(result, "dtype"): + # dtype_n = str(result.dtype) + # else: + # dtype_n = str(np.array([result]).dtype) + # del result + + rep_magnitudes = [] + dtype_n = None + for g in range(nreps): + np.random.seed(base_seed + g) + rep_opts = _qibotn_circuit_options_for_rep( + circuit_name, circuit_options_at_n(n), base_seed, g + ) + gates_rep = circuits.get(circuit_name, n, rep_opts) + circuit_rep = backend.from_qasm(gates_rep.to_qasm()) + result = backend(circuit_rep) + # Use cp.asnumpy if available (cupy), otherwise fallback for numpy + if hasattr(cp, "asnumpy") and isinstance(result, cp.ndarray): + result = cp.asnumpy(result) + else: + result = np.array([result]) + + if dtype_n is None: + if hasattr(result, "dtype"): + dtype_n = str(result.dtype) + else: + dtype_n = str(np.array([result]).dtype) + + if backend.expectation_flag is not None: + rep_magnitudes.append(float(abs(result))) + del result + + if backend.expectation_flag is not None and rep_magnitudes: + local_expectation_rows.append( + (n, float(np.mean(rep_magnitudes))) + ) + + local_meta_rows.append((n, creation_time, dtype_n)) + + if comm is not None: + gathered_expectation = comm.gather(local_expectation_rows, root=0) + gathered_meta = comm.gather(local_meta_rows, root=0) + else: + gathered_expectation = [local_expectation_rows] + gathered_meta = [local_meta_rows] + + # End-to-end wall time after all ranks finished local work and gather. + if comm is not None: + comm.Barrier() + t_sim_end = time.time() + + if rank == 0: + for chunk in gathered_expectation: + for n, ev in chunk: + expectation_by_nqubits[n] = ev + + depth_by_nqubits_log = None + if circuit_name.lower() == "supremacy": + _depth_max = int(_kw.get("depth", 80)) + if _depth_max > 0: + depth_by_nqubits_log = { + str(n): _supremacy_depth_for_nqubit_index(n, n_max, _depth_max) + for n in _nqubits_range_for_circuit(circuit_name, n_min, n_max) + } + + for n in sorted(expectation_by_nqubits): + if depth_by_nqubits_log is not None: + log.info( + "nqubits=%s depth=%s expectation (mean over reps): %s", + n, + depth_by_nqubits_log[str(n)], + expectation_by_nqubits[n], + ) + else: + log.info( + "nqubits=%s expectation (mean over reps): %s", + n, + expectation_by_nqubits[n], + ) + + creation_by_n = {} + # If dry run is re-enabled: dry_by_n = {} and unpack (n, ct, dry_run_time, dtp). + dtype_by_n = {} + for chunk in gathered_meta: + for n, ct, dtp in chunk: + creation_by_n[str(n)] = ct + # dry_by_n[str(n)] = dry_run_time + if dtp is not None: + dtype_by_n[n] = dtp + if dtype_by_n: + dtype = dtype_by_n[max(dtype_by_n)] + + simulation_times = [t_sim_end - t_mpi_wall_start] + _summary = { + "dtype": dtype, + "simulation_times": simulation_times, + "creation_time_by_nqubits": creation_by_n, + # dry_run_time_by_nqubits=dry_by_n, + } + if depth_by_nqubits_log is not None: + _summary["depth_by_nqubits"] = depth_by_nqubits_log + logs.log(**_summary) + logs.average("simulation_times") + if backend.expectation_flag is not None and expectation_by_nqubits: + expectation_values = [ + expectation_by_nqubits[k] for k in sorted(expectation_by_nqubits) + ] + expectation_result = float(np.mean(expectation_values)) + logs.log( + expectation_by_nqubits={str(k): v for k, v in expectation_by_nqubits.items()}, + expectation_result=expectation_result, + ) + logs.dump() + return logs + + +def evolution_benchmark( + nqubits, + dt, + solver, + backend, + platform=None, + nreps=1, + precision="double", + dense=False, + filename=None, +): + """Performs adiabatic evolution with critical TFIM as the hard Hamiltonian.""" + logs = JsonLogger(filename) + logs.log(nqubits=nqubits, nreps=nreps, dt=dt, solver=solver, dense=dense) + + start_time = time.time() + import qibo + + logs.log(import_time=time.time() - start_time) + + qibo.set_backend(backend=backend, platform=platform) + qibo.set_precision(precision) + logs.log( + backend=qibo.get_backend(), + platform=qibo.K.get_platform(), + precision=qibo.get_precision(), + device=qibo.get_device(), + threads=qibo.get_threads(), + version=qibo.__version__, + ) + + from qibo import hamiltonians, models + + start_time = time.time() + h0 = hamiltonians.X(nqubits, dense=dense) + h1 = hamiltonians.TFIM(nqubits, h=1.0, dense=dense) + logs.log(hamiltonian_creation_time=time.time() - start_time) + + start_time = time.time() + evolution = models.AdiabaticEvolution(h0, h1, lambda t: t, dt=dt, solver=solver) + logs.log(evolution_creation_time=time.time() - start_time) + + start_time = time.time() + result = evolution(final_time=1.0) + logs.log(dry_run_time=time.time() - start_time) + dtype = str(result.dtype) + del result + + simulation_times = [] + for _ in range(nreps): + start_time = time.time() + result = evolution(final_time=1.0) + simulation_times.append(time.time() - start_time) + logs.log(dtype=dtype, simulation_times=simulation_times) + logs.average("simulation_times") + logs.dump() + return logs diff --git a/qibojit-benchmarks/benchmarks/tests/__init__.py b/qibojit-benchmarks/benchmarks/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/qibojit-benchmarks/benchmarks/tests/conftest.py b/qibojit-benchmarks/benchmarks/tests/conftest.py new file mode 100644 index 0000000..7d65747 --- /dev/null +++ b/qibojit-benchmarks/benchmarks/tests/conftest.py @@ -0,0 +1,33 @@ +NQUBITS = "3,4,5" +MAX_QUBITS = "0,1,2,3,4" +QIBO_BACKENDS = "qibojit,tensorflow,numpy" +LIBRARIES = "qibo,qiskit,cirq,qsim,tfq,qulacs,projectq,hybridq" + + +def pytest_addoption(parser): + parser.addoption("--nqubits", type=str, default=NQUBITS) + parser.addoption("--max-qubits", type=str, default=MAX_QUBITS) + parser.addoption("--qibo-backends", type=str, default=QIBO_BACKENDS) + parser.addoption("--libraries", type=str, default=LIBRARIES) + parser.addoption("--add", type=str, default="") + + +def pytest_generate_tests(metafunc): + nqubits = [int(n) for n in metafunc.config.option.nqubits.split(",")] + library_options = [f"max_qubits={n}" for n in metafunc.config.option.max_qubits.split(",")] + backends = metafunc.config.option.qibo_backends.split(",") + libraries = metafunc.config.option.libraries.split(",") + additional = metafunc.config.option.add + if additional: + libraries.extend(additional.split(",")) + + if "nqubits" in metafunc.fixturenames: + metafunc.parametrize("nqubits", nqubits) + if "backend" in metafunc.fixturenames: + metafunc.parametrize("backend", backends) + if "library" in metafunc.fixturenames: + metafunc.parametrize("library", libraries) + if "library_options" in metafunc.fixturenames: + metafunc.parametrize("library_options", library_options) + if "transfer" in metafunc.fixturenames: + metafunc.parametrize("transfer", [False, True]) diff --git a/qibojit-benchmarks/benchmarks/tests/graphs/testgraph28.json b/qibojit-benchmarks/benchmarks/tests/graphs/testgraph28.json new file mode 100644 index 0000000..52dcbcb --- /dev/null +++ b/qibojit-benchmarks/benchmarks/tests/graphs/testgraph28.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 6}, {"id": 18}, {"id": 7}, {"id": 20}, {"id": 26}, {"id": 27}, {"id": 12}, {"id": 19}, {"id": 3}, {"id": 13}, {"id": 14}, {"id": 25}, {"id": 5}, {"id": 9}, {"id": 11}, {"id": 17}, {"id": 0}, {"id": 23}, {"id": 10}, {"id": 21}, {"id": 2}, {"id": 8}, {"id": 15}, {"id": 4}, {"id": 1}, {"id": 16}, {"id": 24}, {"id": 22}], "links": [{"source": 6, "target": 18}, {"source": 6, "target": 3}, {"source": 6, "target": 0}, {"source": 18, "target": 14}, {"source": 18, "target": 24}, {"source": 7, "target": 20}, {"source": 7, "target": 10}, {"source": 7, "target": 1}, {"source": 20, "target": 9}, {"source": 20, "target": 21}, {"source": 26, "target": 27}, {"source": 26, "target": 2}, {"source": 26, "target": 3}, {"source": 27, "target": 23}, {"source": 27, "target": 25}, {"source": 12, "target": 19}, {"source": 12, "target": 9}, {"source": 12, "target": 11}, {"source": 19, "target": 5}, {"source": 19, "target": 10}, {"source": 3, "target": 13}, {"source": 13, "target": 4}, {"source": 13, "target": 21}, {"source": 14, "target": 25}, {"source": 14, "target": 15}, {"source": 25, "target": 17}, {"source": 5, "target": 4}, {"source": 5, "target": 24}, {"source": 9, "target": 16}, {"source": 11, "target": 17}, {"source": 11, "target": 1}, {"source": 17, "target": 15}, {"source": 0, "target": 23}, {"source": 0, "target": 1}, {"source": 23, "target": 8}, {"source": 10, "target": 21}, {"source": 2, "target": 8}, {"source": 2, "target": 22}, {"source": 8, "target": 16}, {"source": 15, "target": 22}, {"source": 4, "target": 16}, {"source": 24, "target": 22}]} \ No newline at end of file diff --git a/qibojit-benchmarks/benchmarks/tests/graphs/testgraph8.json b/qibojit-benchmarks/benchmarks/tests/graphs/testgraph8.json new file mode 100644 index 0000000..0ed879c --- /dev/null +++ b/qibojit-benchmarks/benchmarks/tests/graphs/testgraph8.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 0}, {"id": 7}, {"id": 1}, {"id": 2}, {"id": 4}, {"id": 3}, {"id": 5}, {"id": 6}], "links": [{"source": 0, "target": 7}, {"source": 0, "target": 4}, {"source": 0, "target": 3}, {"source": 7, "target": 1}, {"source": 7, "target": 4}, {"source": 1, "target": 2}, {"source": 1, "target": 6}, {"source": 2, "target": 6}, {"source": 2, "target": 5}, {"source": 4, "target": 5}, {"source": 3, "target": 6}, {"source": 3, "target": 5}]} \ No newline at end of file diff --git a/qibojit-benchmarks/benchmarks/tests/test_libraries.py b/qibojit-benchmarks/benchmarks/tests/test_libraries.py new file mode 100644 index 0000000..43de221 --- /dev/null +++ b/qibojit-benchmarks/benchmarks/tests/test_libraries.py @@ -0,0 +1,165 @@ +"""Check that execution of circuits from external simulation libraries agrees with Qibo.""" +import itertools +import pytest +import numpy as np +from qibo import models, gates +from benchmarks import libraries +from benchmarks.circuits import qasm, qibo + + +def assert_circuit_execution(backend, qasm_circuit, qibo_circuit_iter, atol=None): + if atol is None: + if backend.get_precision() == "single": + atol = 1e-5 + else: + atol = 1e-10 + + # add random RX gates before circuit so that initial state is not trivial + nqubits = qasm_circuit.nqubits + theta = np.random.random(nqubits) + qasm_code = qasm_circuit.to_qasm(theta=theta) + + # execute circuit using backend + circuit = backend.from_qasm(qasm_code) + final_state = backend(circuit) + final_state = backend.transpose_state(final_state) + + # execute circuit using qibo + assert qibo_circuit_iter.nqubits == nqubits + target_circuit = models.Circuit(nqubits) + target_circuit.add(gates.RX(i, theta=t) for i, t in enumerate(theta)) + target_circuit.add(qibo_circuit_iter) + target_state = target_circuit() + + # check fidelity instead of absolute states due to different definitions + # of the phase of U gates in different backends + fidelity = np.abs(np.conj(target_state).dot(np.array(final_state))) + np.testing.assert_allclose(fidelity, 1.0, atol=atol) + + +@pytest.mark.parametrize("nlayers", ["1", "4"]) +@pytest.mark.parametrize("gate, qibo_gate", + [("h", "H"), ("x", "X"), ("y", "Y"), ("z", "Z")]) +def test_one_qubit_gate(nqubits, library, nlayers, gate, qibo_gate): + qasm_circuit = qasm.OneQubitGate(nqubits, nlayers=nlayers, gate=gate) + target_circuit = qibo.OneQubitGate(nqubits, nlayers=nlayers, gate=qibo_gate) + backend = libraries.get(library) + assert_circuit_execution(backend, qasm_circuit, target_circuit) + + +@pytest.mark.parametrize("gate,qibo_gate,params", + [("rx", "RX", {"theta": 0.1}), + ("ry", "RY", {"theta": 0.3}), + ("rz", "RZ", {"theta": 0.2}), + ("u1", "U1", {"theta": 0.3}), + ("u2", "U2", {"phi": 0.2, "lam": 0.3}), + ("u3", "U3", {"theta": 0.1, "phi": 0.2, "lam": 0.3})]) +def test_one_qubit_gate_parametrized(nqubits, library, gate, qibo_gate, params): + if gate in {"u1", "u2", "u3"} and library == "tfq": + pytest.skip("Skipping {} test because it is not supported by {}." + "".format(gate, library)) + order = ["theta", "phi", "lam"] + angles = ",".join(str(params.get(n)) for n in order if n in params) + qasm_circuit = qasm.OneQubitGate(nqubits, gate=gate, angles=angles) + target_circuit = qibo.OneQubitGate(nqubits, gate=qibo_gate, **params) + backend = libraries.get(library) + assert_circuit_execution(backend, qasm_circuit, target_circuit) + + +@pytest.mark.parametrize("nlayers", ["1", "4"]) +@pytest.mark.parametrize("gate,qibo_gate", + [("cx", "CNOT"), ("swap", "SWAP"), ("cz", "CZ")]) +def test_two_qubit_gate(nqubits, library, nlayers, gate, qibo_gate): + qasm_circuit = qasm.TwoQubitGate(nqubits, nlayers=nlayers, gate=gate) + target_circuit = qibo.TwoQubitGate(nqubits, nlayers=nlayers, gate=qibo_gate) + backend = libraries.get(library) + assert_circuit_execution(backend, qasm_circuit, target_circuit) + + +@pytest.mark.parametrize("gate,qibo_gate,params", + [("cu1", "CU1", {"theta": 0.3}), + #("cu2", "CU2", {"phi": 0.1, "lam": 0.3}), # not supported by OpenQASM + ("cu3", "CU3", {"theta": 0.1, "phi": 0.2, "lam": 0.3})]) +def test_two_qubit_gate_parametrized(nqubits, library, gate, qibo_gate, params): + if gate in {"cu1", "cu2", "cu3"} and library == "tfq": + pytest.skip("Skipping {} test because it is not supported by {}." + "".format(gate, library)) + if gate in {"cu3"} and library == "projectq": + pytest.skip("Skipping {} test because it is not supported by {}." + "".format(gate, library)) + + order = ["theta", "phi", "lam"] + angles = ",".join(str(params.get(n)) for n in order if n in params) + qasm_circuit = qasm.TwoQubitGate(nqubits, gate=gate, angles=angles) + target_circuit = qibo.TwoQubitGate(nqubits, gate=qibo_gate, **params) + backend = libraries.get(library) + assert_circuit_execution(backend, qasm_circuit, target_circuit) + + +@pytest.mark.parametrize("swaps", ["False", "True"]) +def test_qft(nqubits, library, swaps, library_options): + qasm_circuit = qasm.QFT(nqubits, swaps=swaps) + target_circuit = qibo.QFT(nqubits, swaps=swaps) + backend = libraries.get(library, library_options) + assert_circuit_execution(backend, qasm_circuit, target_circuit) + + +@pytest.mark.parametrize("nlayers", ["2", "5"]) +def test_variational(nqubits, library, nlayers, library_options): + qasm_circuit = qasm.VariationalCircuit(nqubits, nlayers=nlayers) + target_circuit = qibo.VariationalCircuit(nqubits, nlayers=nlayers) + backend = libraries.get(library, library_options) + assert_circuit_execution(backend, qasm_circuit, target_circuit) + + +def test_bernstein_vazirani(nqubits, library, library_options): + qasm_circuit = qasm.BernsteinVazirani(nqubits) + target_circuit = qibo.BernsteinVazirani(nqubits) + backend = libraries.get(library, library_options) + assert_circuit_execution(backend, qasm_circuit, target_circuit) + + +def test_hidden_shift(nqubits, library, library_options): + shift = "".join(str(x) for x in np.random.randint(0, 2, size=(nqubits,))) + qasm_circuit = qasm.HiddenShift(nqubits, shift=shift) + target_circuit = qibo.HiddenShift(nqubits, shift=shift) + backend = libraries.get(library, library_options) + assert_circuit_execution(backend, qasm_circuit, target_circuit) + + +def test_qaoa_circuit(library, library_options): + if library in {"qibo", "qcgpu"}: + pytest.skip(f"{library} does not have built-in RZZ gate.") + import pathlib + folder = str(pathlib.Path(__file__).with_name("graphs") / "testgraph8.json") + qasm_circuit = qasm.QAOA(8, graph=folder) + target_circuit = qibo.QAOA(8, graph=folder) + backend = libraries.get(library, library_options) + assert_circuit_execution(backend, qasm_circuit, target_circuit) + + +@pytest.mark.parametrize("depth", ["2", "5", "10"]) +def test_supremacy_circuit(nqubits, library, depth, library_options): + qasm_circuit = qasm.SupremacyCircuit(nqubits, depth=depth) + target_circuit = qibo.SupremacyCircuit(nqubits, depth=depth) + backend = libraries.get(library, library_options) + assert_circuit_execution(backend, qasm_circuit, target_circuit) + + +@pytest.mark.parametrize("simtime", ["1", "2.5"]) +def test_basis_change(nqubits, library, simtime, library_options): + qasm_circuit = qasm.BasisChange(nqubits, simulation_time=simtime) + target_circuit = qibo.BasisChange(nqubits, simulation_time=simtime) + backend = libraries.get(library, library_options) + assert_circuit_execution(backend, qasm_circuit, target_circuit) + + +@pytest.mark.parametrize("depth", ["2", "5", "8"]) +def test_quantum_volume(nqubits, library, depth, library_options): + if library == "tfq": + pytest.skip("Skipping qv test because it is not supported by {}." + "".format(library)) + qasm_circuit = qasm.QuantumVolume(nqubits, depth=depth) + target_circuit = qibo.QuantumVolume(nqubits, depth=depth) + backend = libraries.get(library, library_options) + assert_circuit_execution(backend, qasm_circuit, target_circuit) diff --git a/qibojit-benchmarks/benchmarks/tests/test_qibo_benchmarks.py b/qibojit-benchmarks/benchmarks/tests/test_qibo_benchmarks.py new file mode 100644 index 0000000..c638f36 --- /dev/null +++ b/qibojit-benchmarks/benchmarks/tests/test_qibo_benchmarks.py @@ -0,0 +1,166 @@ +import pytest +from benchmarks.scripts import circuit_benchmark, evolution_benchmark + + +def assert_logs(logs, nqubits, backend, nreps=1): + assert logs[-1]["nqubits"] == nqubits + assert logs[-1]["backend"] == backend + assert logs[-1]["simulation_times_mean"] >= 0 + assert logs[-1]["transfer_times_mean"] >= 0 + assert len(logs[-1]["simulation_times"]) == nreps + assert len(logs[-1]["transfer_times"]) == nreps + + +@pytest.mark.parametrize("nreps", [1, 5]) +@pytest.mark.parametrize("nlayers", ["1", "4"]) +@pytest.mark.parametrize("gate", ["H", "X", "Y", "Z"]) +def test_one_qubit_gate_benchmark(nqubits, backend, transfer, nreps, + nlayers, gate): + logs = circuit_benchmark(nqubits, backend, circuit_name="one-qubit-gate", + nreps=nreps, transfer=transfer, + circuit_options=f"gate={gate},nlayers={nlayers}") + assert_logs(logs, nqubits, backend, nreps) + target_options = f"nqubits={nqubits}, nlayers={nlayers}, " + target_options += f"gate={gate}, params={{}}" + assert logs[-1]["circuit"] == "one-qubit-gate" + assert logs[-1]["circuit_options"] == target_options + + +@pytest.mark.parametrize("gate,params", + [("RX", "theta=0.1"), ("RZ", "theta=0.2"), + ("U1", "theta=0.3"), ("U2", "phi=0.2,lam=0.3"), + ("U3", "theta=0.1,phi=0.2,lam=0.3")]) +def test_one_qubit_gate_param_benchmark(nqubits, backend, gate, params): + logs = circuit_benchmark(nqubits, backend, circuit_name="one-qubit-gate", + circuit_options=f"gate={gate},{params}") + assert_logs(logs, nqubits, backend) + target_options = f"nqubits={nqubits}, nlayers=1, gate={gate}" + paramdict = {} + for param in params.split(","): + k, v = param.split("=") + paramdict[k] = v + target_options = f"{target_options}, params={paramdict}" + assert logs[-1]["circuit"] == "one-qubit-gate" + assert logs[-1]["circuit_options"] == target_options + + +@pytest.mark.parametrize("nreps", [1, 5]) +@pytest.mark.parametrize("nlayers", ["1", "4"]) +@pytest.mark.parametrize("gate", ["CNOT", "SWAP", "CZ"]) +def test_two_qubit_gate_benchmark(nqubits, backend, transfer, nreps, + nlayers, gate): + logs = circuit_benchmark(nqubits, backend, circuit_name="two-qubit-gate", + nreps=nreps, transfer=transfer, + circuit_options=f"gate={gate},nlayers={nlayers}") + assert_logs(logs, nqubits, backend, nreps) + target_options = f"nqubits={nqubits}, nlayers={nlayers}, " + target_options += f"gate={gate}, params={{}}" + assert logs[-1]["circuit"] == "two-qubit-gate" + assert logs[-1]["circuit_options"] == target_options + + +@pytest.mark.parametrize("gate,params", + [("CRX", "theta=0.1"), ("CRZ", "theta=0.2"), + ("CU1", "theta=0.3"), ("CU2", "phi=0.2,lam=0.3"), + ("CU3", "theta=0.1,phi=0.2,lam=0.3"), + ("fSim", "theta=0.1,phi=0.2")]) +def test_two_qubit_gate_param_benchmark(nqubits, backend, gate, params): + logs = circuit_benchmark(nqubits, backend, circuit_name="two-qubit-gate", + circuit_options=f"gate={gate},{params}") + assert_logs(logs, nqubits, backend) + target_options = f"nqubits={nqubits}, nlayers=1, gate={gate}" + paramdict = {} + for param in params.split(","): + k, v = param.split("=") + paramdict[k] = v + target_options = f"{target_options}, params={paramdict}" + assert logs[-1]["circuit"] == "two-qubit-gate" + assert logs[-1]["circuit_options"] == target_options + + +@pytest.mark.parametrize("nreps", [1, 5]) +@pytest.mark.parametrize("swaps", [False, True]) +def test_qft_benchmark(nqubits, backend, transfer, nreps, swaps): + logs = circuit_benchmark(nqubits, backend, circuit_name="qft", + nreps=nreps, transfer=transfer, + circuit_options=f"swaps={swaps}") + assert_logs(logs, nqubits, backend, nreps) + target_options = f"nqubits={nqubits}, swaps={swaps}" + assert logs[-1]["circuit"] == "qft" + assert logs[-1]["circuit_options"] == target_options + + +@pytest.mark.parametrize("varlayer", [False, True]) +def test_variational_benchmark(nqubits, backend, varlayer): + logs = circuit_benchmark(nqubits, backend, circuit_name="variational", + circuit_options=f"varlayer={varlayer}") + assert_logs(logs, nqubits, backend) + target_options = f"nqubits={nqubits}, nlayers=1, seed=123, varlayer={varlayer}" + assert logs[-1]["circuit"] == "variational" + assert logs[-1]["circuit_options"] == target_options + + +def test_bernstein_vazirani_benchmark(nqubits, backend): + logs = circuit_benchmark(nqubits, backend, circuit_name="bv") + assert_logs(logs, nqubits, backend) + assert logs[-1]["circuit"] == "bv" + assert logs[-1]["circuit_options"] == f"nqubits={nqubits}" + + +@pytest.mark.parametrize("random", [True, False]) +def test_hidden_shift_benchmark(nqubits, backend, random): + shift = "" if random else nqubits * "0" + logs = circuit_benchmark(nqubits, backend, circuit_name="hs", + circuit_options=f"shift={shift}") + assert_logs(logs, nqubits, backend) + target_options = f"nqubits={nqubits}, shift={shift}" + assert logs[-1]["circuit"] == "hs" + assert logs[-1]["circuit_options"] == target_options + + +def test_qaoa_benchmark(backend): + logs = circuit_benchmark(4, backend, circuit_name="qaoa") + assert_logs(logs, 4, backend) + target_options = f"nqubits=4, nparams=2, graph=, seed=123" + assert logs[-1]["circuit"] == "qaoa" + assert logs[-1]["circuit_options"] == target_options + + +@pytest.mark.parametrize("depth", ["2", "5", "10"]) +def test_supremacy_benchmark(nqubits, backend, depth): + logs = circuit_benchmark(nqubits, backend, circuit_name="supremacy", + circuit_options=f"depth={depth}") + assert_logs(logs, nqubits, backend) + target_options = f"nqubits={nqubits}, depth={depth}, seed=123" + assert logs[-1]["circuit"] == "supremacy" + assert logs[-1]["circuit_options"] == target_options + + +@pytest.mark.parametrize("simtime", ["1", "2.5"]) +def test_basis_change_benchmark(nqubits, backend, simtime): + logs = circuit_benchmark(nqubits, backend, circuit_name="bc", + circuit_options=f"simulation_time={simtime}") + assert_logs(logs, nqubits, backend) + target_options = f"nqubits={nqubits}, simulation_time={simtime}, seed=123" + assert logs[-1]["circuit"] == "bc" + assert logs[-1]["circuit_options"] == target_options + + +@pytest.mark.parametrize("depth", ["2", "5", "8"]) +def test_quantum_volume_benchmark(nqubits, backend, depth): + logs = circuit_benchmark(nqubits, backend, circuit_name="qv", + circuit_options=f"depth={depth}") + assert_logs(logs, nqubits, backend) + target_options = f"nqubits={nqubits}, depth={depth}, seed=123" + assert logs[-1]["circuit"] == "qv" + assert logs[-1]["circuit_options"] == target_options + + +@pytest.mark.parametrize("dt", [0.01, 0.05, 0.1]) +@pytest.mark.parametrize("dense", [False, True]) +def test_adiabatic_evolution_benchmark(nqubits, dt, backend, dense, solver="exp"): + logs = evolution_benchmark(nqubits, dt, solver, backend, dense=dense) + assert logs[-1]["nqubits"] == nqubits + assert logs[-1]["dt"] == dt + assert logs[-1]["backend"] == backend + assert logs[-1]["dense"] == dense diff --git a/qibojit-benchmarks/benchmarks/tests/test_qibo_circuits.py b/qibojit-benchmarks/benchmarks/tests/test_qibo_circuits.py new file mode 100644 index 0000000..565e726 --- /dev/null +++ b/qibojit-benchmarks/benchmarks/tests/test_qibo_circuits.py @@ -0,0 +1,112 @@ +"""Tests for circuits defined in benchmarks/circuits/qibo.py""" + +import pytest +from qibo.models import Circuit +from benchmarks.circuits import qibo + + +@pytest.mark.parametrize("nlayers", [1, 2, 3, 4, 5]) +@pytest.mark.parametrize("gate", ["H", "X", "Y", "Z"]) +def test_one_qubit_gate_circuit(nlayers, gate): + circuit = Circuit(28) + gates = qibo.OneQubitGate(28, nlayers=nlayers, gate=gate) + circuit.add(gates) + assert circuit.nqubits == 28 + assert circuit.depth == nlayers + assert circuit.ngates == nlayers * 28 + + +@pytest.mark.parametrize("nlayers", [1, 2, 3, 4, 5]) +@pytest.mark.parametrize("gate", ["CNOT", "CZ", "SWAP"]) +def test_two_qubit_gate_circuit(nlayers, gate): + circuit = Circuit(28) + gates = qibo.TwoQubitGate(28, nlayers=nlayers, gate=gate) + circuit.add(gates) + assert circuit.nqubits == 28 + assert circuit.depth == nlayers * 2 + assert circuit.ngates == nlayers * 27 + + +@pytest.mark.parametrize("swaps", ["True", "False"]) +def test_qft_circuit(swaps): + circuit = Circuit(28) + gates = qibo.QFT(28, swaps=swaps) + circuit.add(gates) + assert circuit.nqubits == 28 + if swaps == "True": + assert circuit.depth == 56 + assert circuit.ngates == 420 + else: + assert circuit.depth == 55 + assert circuit.ngates == 406 + + +@pytest.mark.parametrize("varlayer", ["True", "False"]) +def test_variational_circuit(varlayer): + circuit = Circuit(28) + gates = qibo.VariationalCircuit(28, varlayer=varlayer) + circuit.add(gates) + assert circuit.nqubits == 28 + if varlayer == "True": + assert circuit.depth == 2 + assert circuit.ngates == 28 + else: + assert circuit.depth == 4 + assert circuit.ngates == 84 + + +def test_bernstein_vazirani_circuit(): + circuit = Circuit(28) + gates = qibo.BernsteinVazirani(28) + circuit.add(gates) + assert circuit.nqubits == 28 + assert circuit.depth == 30 + assert circuit.ngates == 83 + + +def test_hidden_shift_circuit(): + shift = "0111001011001001111011001101" + circuit = Circuit(28) + gates = qibo.HiddenShift(28, shift=shift) + circuit.add(gates) + assert circuit.nqubits == 28 + assert circuit.depth == 7 + assert circuit.ngates == 144 + + +def test_qaoa_circuit(): + import pathlib + folder = str(pathlib.Path(__file__).with_name("graphs") / "testgraph28.json") + circuit = Circuit(28) + gates = qibo.QAOA(28, graph=folder) + circuit.add(gates) + assert circuit.nqubits == 28 + assert circuit.ngates == 168 + assert circuit.depth == 18 + + +def test_supremacy_circuit(): + circuit = Circuit(28) + gates = qibo.SupremacyCircuit(28, depth="40") + circuit.add(gates) + assert circuit.nqubits == 28 + assert circuit.ngates == 880 + assert circuit.depth == 42 + + +def test_basis_change_circuit(): + circuit = Circuit(28) + gates = qibo.BasisChange(28) + circuit.add(gates) + assert circuit.nqubits == 28 + assert circuit.ngates == 9912 + assert circuit.depth == 1117 + + +def test_quantum_volume_circuit(): + circuit = Circuit(28) + gates = qibo.QuantumVolume(28, depth=10) + circuit.add(gates) + assert circuit.nqubits == 28 + assert circuit.ngates == 1540 + assert circuit.depth == 70 diff --git a/qibojit-benchmarks/benchmarks/utils.py b/qibojit-benchmarks/benchmarks/utils.py new file mode 100644 index 0000000..d9c0a05 --- /dev/null +++ b/qibojit-benchmarks/benchmarks/utils.py @@ -0,0 +1,25 @@ +def limit_gpu_memory(memory_limit=None): + """Limits GPU memory that is available to Tensorflow. + Args: + memory_limit: Memory limit in MBs. + """ + import tensorflow as tf + if memory_limit is None: + print("\nNo GPU memory limiter used.\n") + return + + print("\nAttempting to limit GPU memory to {}.\n".format(memory_limit)) + for gpu in tf.config.list_physical_devices("GPU"): + config = tf.config.experimental.VirtualDeviceConfiguration( + memory_limit=memory_limit) + tf.config.experimental.set_virtual_device_configuration(gpu, [config]) + print("Limiting memory of {} to {}.".format(gpu.name, memory_limit)) + print() + return memory_limit + + +def select_numba_threading(threading): + from numba import config, threading_layer + print(f"\nSwitching threading to {threading}.\n") + config.THREADING_LAYER = threading + return threading_layer diff --git a/qibojit-benchmarks/compare.py b/qibojit-benchmarks/compare.py new file mode 100644 index 0000000..9854d14 --- /dev/null +++ b/qibojit-benchmarks/compare.py @@ -0,0 +1,78 @@ +"""Launches the circuit benchmark script for user given arguments.""" + +import argparse +from benchmarks.scripts import library_benchmark, qibotn_benchmark + + +parser = argparse.ArgumentParser() +parser.add_argument( + "--nqubits", + nargs=2, + type=int, + metavar=("MIN", "MAX"), + default=[10, 10], + help="Inclusive qubit-count range MIN MAX. Each size is run; per-size " + "expectations are printed, and their arithmetic mean is logged as expectation_result.", +) +parser.add_argument( + "--library", + default="qibo", + type=str, + help="Quantum simulation library to use in benchmark. " + "See README for the list of available libraries.", +) +parser.add_argument( + "--library-options", + default=None, + type=str, + help="String with options for the library. " + "It should have the form 'arg1=value1,arg2=value2,...'. " + "Each library supports different options.", +) + +parser.add_argument( + "--circuit", + default="qft", + type=str, + help="Type of circuit to use. See README for the list of " "available circuits.", +) +parser.add_argument( + "--circuit-options", + default=None, + type=str, + help="String with options for circuit creation. " + "It should have the form 'arg1=value1,arg2=value2,...'. " + "See README for the list of arguments that are " + "available for each circuit.", +) +parser.add_argument( + "--precision", + default=None, + type=str, + help="Numerical precision of the simulation. " + "Choose between 'double' and 'single'.", +) + +parser.add_argument( + "--nreps", + default=1, + type=int, + help="Number of repetitions of the circuit execution. " "Dry run is not included.", +) +# parser.add_argument("--transfer", action="store_true", +# help="If used the final state array is converted to numpy. " +# "If the simulation device is GPU this requires a " +# "transfer from GPU memory to CPU.") + +parser.add_argument( + "--filename", + default=None, + type=str, + help="Directory of file to save the logs in json format. " + "If not given the logs will only be printed and not saved.", +) + +if __name__ == "__main__": + args = vars(parser.parse_args()) + args["circuit_name"] = args.pop("circuit") + qibotn_benchmark(**args) diff --git a/qibojit-benchmarks/cu_tensornet_expectation.json b/qibojit-benchmarks/cu_tensornet_expectation.json new file mode 100644 index 0000000..1c124dc --- /dev/null +++ b/qibojit-benchmarks/cu_tensornet_expectation.json @@ -0,0 +1 @@ +{"MPI_enabled": false, "MPS_enabled": false, "NCCL_enabled": false, "expectation_enabled": true} diff --git a/qibojit-benchmarks/cu_tensornet_mpi_expectation.json b/qibojit-benchmarks/cu_tensornet_mpi_expectation.json new file mode 100644 index 0000000..079958f --- /dev/null +++ b/qibojit-benchmarks/cu_tensornet_mpi_expectation.json @@ -0,0 +1 @@ +{"MPI_enabled": true, "MPS_enabled": false, "NCCL_enabled": false, "expectation_enabled": true} diff --git a/qibojit-benchmarks/cu_tensornet_nccl_expectation.json b/qibojit-benchmarks/cu_tensornet_nccl_expectation.json new file mode 100644 index 0000000..3dd9e5c --- /dev/null +++ b/qibojit-benchmarks/cu_tensornet_nccl_expectation.json @@ -0,0 +1 @@ +{"MPI_enabled": false, "MPS_enabled": false, "NCCL_enabled": true, "expectation_enabled": true} diff --git a/qibojit-benchmarks/environment.yml b/qibojit-benchmarks/environment.yml new file mode 100644 index 0000000..a315a08 --- /dev/null +++ b/qibojit-benchmarks/environment.yml @@ -0,0 +1,18 @@ +name: benchmarks +channels: + - conda-forge +dependencies: + - python=3.12.12 + - pip + - numpy=2.2.6 + - cryptography + - pip: + - qibo==0.2.23 + - qibojit==0.1.12 + - qiskit==1.4.5 + - qulacs==0.6.12 + - cirq==0.8.2 + - qsimcirq==0.22.0 + - qcgpu==0.1.1 + - projectq==0.8.0 + diff --git a/qibojit-benchmarks/evolution.py b/qibojit-benchmarks/evolution.py new file mode 100644 index 0000000..00910cf --- /dev/null +++ b/qibojit-benchmarks/evolution.py @@ -0,0 +1,36 @@ +"""Launches the adiabatic evolution benchmark script for user given arguments.""" +import argparse +from benchmarks.scripts import evolution_benchmark + + +parser = argparse.ArgumentParser() +parser.add_argument("--nqubits", default=4, type=int, + help="Number of qubits in the system to evolve.") +parser.add_argument("--dt", default=1e-2, type=float, + help="Time step size to use for time discretization.") + +parser.add_argument("--nreps", default=1, type=int, + help="Number of repetitions to run the evolution.") +parser.add_argument("--precision", default=None, type=str, + help="Numerical precision of the simulation. " + "Choose between 'double' and 'single'.") + +parser.add_argument("--solver", default="exp", type=str, + help="Which solver to use for integration (exponential or RK methods)") +parser.add_argument("--dense", action="store_true", + help="If ``True`` it uses the full Hamiltonian matrix " + "otherwise the Trotter decomposition is used.") + +parser.add_argument("--backend", default="qibojit", type=str, + help="Qibo backend to use.") +parser.add_argument("--platform", default=None, type=str, + help="Qibo platform to use.") + +parser.add_argument("--filename", default=None, type=str, + help="Directory of file to save the logs in json format. " + "If not given the logs will only be printed and not saved.") + + +if __name__ == "__main__": + args = vars(parser.parse_args()) + evolution_benchmark(**args) diff --git a/qibojit-benchmarks/graphs/generate_random_bitstrings.py b/qibojit-benchmarks/graphs/generate_random_bitstrings.py new file mode 100644 index 0000000..2db7fe2 --- /dev/null +++ b/qibojit-benchmarks/graphs/generate_random_bitstrings.py @@ -0,0 +1,14 @@ +"""Generate random bitstrings for Hidden Shift circuit benchmarks.""" +import numpy as np + + +file = open("random_bitstrings.dat", "w") + +for nqubits in range(3, 31): + bitstring = np.random.randint(0, 2, (nqubits,)) + bitstring = "".join(str(x) for x in bitstring) + file.write(bitstring) + file.write("\n") + print(bitstring) + +file.close() diff --git a/qibojit-benchmarks/graphs/generate_random_graphs.py b/qibojit-benchmarks/graphs/generate_random_graphs.py new file mode 100644 index 0000000..31f2066 --- /dev/null +++ b/qibojit-benchmarks/graphs/generate_random_graphs.py @@ -0,0 +1,10 @@ +"""Generate random regular graphs for MaxCut QAOA benchmark.""" +import json +import networkx + + +for nqubits in range(4, 31, 2): + graph = networkx.random_regular_graph(3, nqubits) + data = networkx.readwrite.json_graph.node_link_data(graph) + with open(f"randomgraph_3_{nqubits}.json", "w") as file: + json.dump(data, file) diff --git a/qibojit-benchmarks/graphs/random_bitstrings.dat b/qibojit-benchmarks/graphs/random_bitstrings.dat new file mode 100644 index 0000000..fe8f52b --- /dev/null +++ b/qibojit-benchmarks/graphs/random_bitstrings.dat @@ -0,0 +1,28 @@ +000 +0110 +00010 +011010 +0010011 +10111000 +101000010 +1110011010 +01001000110 +010011111010 +0011001110100 +01101110111100 +110001101010101 +0000110011101011 +00110100010101000 +000010011001111100 +1110111100110011000 +01101111001101001001 +111110101000010010001 +1011010011001011101001 +10011000100101000110001 +101111110110001101000111 +0110111111101111111001001 +01000011001001100111110001 +000111000000000010100110001 +1011010001001001110010000010 +11111111010100110110111110011 +000101101000010010011000110011 diff --git a/qibojit-benchmarks/graphs/randomgraph_3_10.json b/qibojit-benchmarks/graphs/randomgraph_3_10.json new file mode 100644 index 0000000..216907e --- /dev/null +++ b/qibojit-benchmarks/graphs/randomgraph_3_10.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 1}, {"id": 3}, {"id": 8}, {"id": 0}, {"id": 4}, {"id": 2}, {"id": 7}, {"id": 6}, {"id": 5}, {"id": 9}], "links": [{"source": 1, "target": 3}, {"source": 1, "target": 4}, {"source": 1, "target": 7}, {"source": 3, "target": 8}, {"source": 3, "target": 2}, {"source": 8, "target": 6}, {"source": 8, "target": 9}, {"source": 0, "target": 4}, {"source": 0, "target": 2}, {"source": 0, "target": 5}, {"source": 4, "target": 6}, {"source": 2, "target": 7}, {"source": 7, "target": 5}, {"source": 6, "target": 9}, {"source": 5, "target": 9}]} \ No newline at end of file diff --git a/qibojit-benchmarks/graphs/randomgraph_3_12.json b/qibojit-benchmarks/graphs/randomgraph_3_12.json new file mode 100644 index 0000000..2effd12 --- /dev/null +++ b/qibojit-benchmarks/graphs/randomgraph_3_12.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 0}, {"id": 1}, {"id": 9}, {"id": 10}, {"id": 2}, {"id": 5}, {"id": 11}, {"id": 6}, {"id": 8}, {"id": 3}, {"id": 4}, {"id": 7}], "links": [{"source": 0, "target": 1}, {"source": 0, "target": 10}, {"source": 0, "target": 3}, {"source": 1, "target": 2}, {"source": 1, "target": 4}, {"source": 9, "target": 10}, {"source": 9, "target": 7}, {"source": 9, "target": 3}, {"source": 10, "target": 8}, {"source": 2, "target": 3}, {"source": 2, "target": 11}, {"source": 5, "target": 11}, {"source": 5, "target": 7}, {"source": 5, "target": 4}, {"source": 11, "target": 8}, {"source": 6, "target": 8}, {"source": 6, "target": 4}, {"source": 6, "target": 7}]} \ No newline at end of file diff --git a/qibojit-benchmarks/graphs/randomgraph_3_14.json b/qibojit-benchmarks/graphs/randomgraph_3_14.json new file mode 100644 index 0000000..efcb12d --- /dev/null +++ b/qibojit-benchmarks/graphs/randomgraph_3_14.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 4}, {"id": 12}, {"id": 5}, {"id": 7}, {"id": 10}, {"id": 1}, {"id": 3}, {"id": 2}, {"id": 11}, {"id": 0}, {"id": 8}, {"id": 9}, {"id": 13}, {"id": 6}], "links": [{"source": 4, "target": 12}, {"source": 4, "target": 8}, {"source": 4, "target": 10}, {"source": 12, "target": 5}, {"source": 12, "target": 0}, {"source": 5, "target": 7}, {"source": 5, "target": 10}, {"source": 7, "target": 9}, {"source": 7, "target": 6}, {"source": 10, "target": 13}, {"source": 1, "target": 3}, {"source": 1, "target": 0}, {"source": 1, "target": 11}, {"source": 3, "target": 8}, {"source": 3, "target": 2}, {"source": 2, "target": 11}, {"source": 2, "target": 6}, {"source": 11, "target": 0}, {"source": 8, "target": 13}, {"source": 9, "target": 13}, {"source": 9, "target": 6}]} \ No newline at end of file diff --git a/qibojit-benchmarks/graphs/randomgraph_3_16.json b/qibojit-benchmarks/graphs/randomgraph_3_16.json new file mode 100644 index 0000000..3cb7eb3 --- /dev/null +++ b/qibojit-benchmarks/graphs/randomgraph_3_16.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 4}, {"id": 12}, {"id": 5}, {"id": 13}, {"id": 15}, {"id": 3}, {"id": 10}, {"id": 8}, {"id": 1}, {"id": 6}, {"id": 0}, {"id": 14}, {"id": 2}, {"id": 11}, {"id": 9}, {"id": 7}], "links": [{"source": 4, "target": 12}, {"source": 4, "target": 15}, {"source": 4, "target": 14}, {"source": 12, "target": 8}, {"source": 12, "target": 9}, {"source": 5, "target": 13}, {"source": 5, "target": 10}, {"source": 5, "target": 9}, {"source": 13, "target": 3}, {"source": 13, "target": 15}, {"source": 15, "target": 0}, {"source": 3, "target": 9}, {"source": 3, "target": 2}, {"source": 10, "target": 11}, {"source": 10, "target": 2}, {"source": 8, "target": 2}, {"source": 8, "target": 7}, {"source": 1, "target": 6}, {"source": 1, "target": 0}, {"source": 1, "target": 14}, {"source": 6, "target": 11}, {"source": 6, "target": 7}, {"source": 0, "target": 14}, {"source": 11, "target": 7}]} \ No newline at end of file diff --git a/qibojit-benchmarks/graphs/randomgraph_3_18.json b/qibojit-benchmarks/graphs/randomgraph_3_18.json new file mode 100644 index 0000000..ef1872c --- /dev/null +++ b/qibojit-benchmarks/graphs/randomgraph_3_18.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 4}, {"id": 12}, {"id": 0}, {"id": 2}, {"id": 5}, {"id": 9}, {"id": 17}, {"id": 11}, {"id": 6}, {"id": 8}, {"id": 15}, {"id": 3}, {"id": 16}, {"id": 14}, {"id": 10}, {"id": 13}, {"id": 1}, {"id": 7}], "links": [{"source": 4, "target": 12}, {"source": 4, "target": 8}, {"source": 4, "target": 7}, {"source": 12, "target": 15}, {"source": 12, "target": 9}, {"source": 0, "target": 2}, {"source": 0, "target": 5}, {"source": 0, "target": 3}, {"source": 2, "target": 11}, {"source": 2, "target": 13}, {"source": 5, "target": 3}, {"source": 5, "target": 14}, {"source": 9, "target": 17}, {"source": 9, "target": 16}, {"source": 17, "target": 6}, {"source": 17, "target": 1}, {"source": 11, "target": 6}, {"source": 11, "target": 16}, {"source": 6, "target": 3}, {"source": 8, "target": 14}, {"source": 8, "target": 16}, {"source": 15, "target": 7}, {"source": 15, "target": 13}, {"source": 14, "target": 10}, {"source": 10, "target": 13}, {"source": 10, "target": 1}, {"source": 1, "target": 7}]} \ No newline at end of file diff --git a/qibojit-benchmarks/graphs/randomgraph_3_20.json b/qibojit-benchmarks/graphs/randomgraph_3_20.json new file mode 100644 index 0000000..10faab8 --- /dev/null +++ b/qibojit-benchmarks/graphs/randomgraph_3_20.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 12}, {"id": 16}, {"id": 4}, {"id": 9}, {"id": 14}, {"id": 17}, {"id": 10}, {"id": 18}, {"id": 0}, {"id": 11}, {"id": 2}, {"id": 6}, {"id": 8}, {"id": 7}, {"id": 19}, {"id": 3}, {"id": 15}, {"id": 5}, {"id": 1}, {"id": 13}], "links": [{"source": 12, "target": 16}, {"source": 12, "target": 4}, {"source": 12, "target": 5}, {"source": 16, "target": 7}, {"source": 16, "target": 15}, {"source": 4, "target": 0}, {"source": 4, "target": 7}, {"source": 9, "target": 14}, {"source": 9, "target": 17}, {"source": 9, "target": 3}, {"source": 14, "target": 2}, {"source": 14, "target": 6}, {"source": 17, "target": 11}, {"source": 17, "target": 8}, {"source": 10, "target": 18}, {"source": 10, "target": 8}, {"source": 10, "target": 13}, {"source": 18, "target": 0}, {"source": 18, "target": 11}, {"source": 0, "target": 11}, {"source": 2, "target": 1}, {"source": 2, "target": 6}, {"source": 6, "target": 8}, {"source": 7, "target": 19}, {"source": 19, "target": 13}, {"source": 19, "target": 15}, {"source": 3, "target": 15}, {"source": 3, "target": 5}, {"source": 5, "target": 1}, {"source": 1, "target": 13}]} \ No newline at end of file diff --git a/qibojit-benchmarks/graphs/randomgraph_3_22.json b/qibojit-benchmarks/graphs/randomgraph_3_22.json new file mode 100644 index 0000000..bb46998 --- /dev/null +++ b/qibojit-benchmarks/graphs/randomgraph_3_22.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 6}, {"id": 12}, {"id": 3}, {"id": 4}, {"id": 7}, {"id": 13}, {"id": 19}, {"id": 18}, {"id": 8}, {"id": 15}, {"id": 0}, {"id": 14}, {"id": 21}, {"id": 1}, {"id": 9}, {"id": 20}, {"id": 10}, {"id": 5}, {"id": 2}, {"id": 11}, {"id": 16}, {"id": 17}], "links": [{"source": 6, "target": 12}, {"source": 6, "target": 20}, {"source": 6, "target": 19}, {"source": 12, "target": 13}, {"source": 12, "target": 2}, {"source": 3, "target": 4}, {"source": 3, "target": 7}, {"source": 3, "target": 19}, {"source": 4, "target": 18}, {"source": 4, "target": 2}, {"source": 7, "target": 0}, {"source": 7, "target": 9}, {"source": 13, "target": 20}, {"source": 13, "target": 18}, {"source": 19, "target": 0}, {"source": 18, "target": 15}, {"source": 8, "target": 15}, {"source": 8, "target": 21}, {"source": 8, "target": 14}, {"source": 15, "target": 20}, {"source": 0, "target": 14}, {"source": 14, "target": 21}, {"source": 21, "target": 5}, {"source": 1, "target": 9}, {"source": 1, "target": 11}, {"source": 1, "target": 17}, {"source": 9, "target": 10}, {"source": 10, "target": 11}, {"source": 10, "target": 16}, {"source": 5, "target": 11}, {"source": 5, "target": 17}, {"source": 2, "target": 16}, {"source": 16, "target": 17}]} \ No newline at end of file diff --git a/qibojit-benchmarks/graphs/randomgraph_3_24.json b/qibojit-benchmarks/graphs/randomgraph_3_24.json new file mode 100644 index 0000000..c0ab5bf --- /dev/null +++ b/qibojit-benchmarks/graphs/randomgraph_3_24.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 6}, {"id": 15}, {"id": 22}, {"id": 23}, {"id": 3}, {"id": 13}, {"id": 12}, {"id": 5}, {"id": 16}, {"id": 9}, {"id": 14}, {"id": 0}, {"id": 8}, {"id": 10}, {"id": 18}, {"id": 11}, {"id": 17}, {"id": 1}, {"id": 7}, {"id": 19}, {"id": 4}, {"id": 21}, {"id": 20}, {"id": 2}], "links": [{"source": 6, "target": 15}, {"source": 6, "target": 5}, {"source": 6, "target": 10}, {"source": 15, "target": 8}, {"source": 15, "target": 1}, {"source": 22, "target": 23}, {"source": 22, "target": 12}, {"source": 22, "target": 13}, {"source": 23, "target": 4}, {"source": 23, "target": 19}, {"source": 3, "target": 13}, {"source": 3, "target": 21}, {"source": 3, "target": 14}, {"source": 13, "target": 2}, {"source": 12, "target": 9}, {"source": 12, "target": 2}, {"source": 5, "target": 16}, {"source": 5, "target": 21}, {"source": 16, "target": 7}, {"source": 16, "target": 11}, {"source": 9, "target": 14}, {"source": 9, "target": 21}, {"source": 14, "target": 20}, {"source": 0, "target": 8}, {"source": 0, "target": 17}, {"source": 0, "target": 4}, {"source": 8, "target": 7}, {"source": 10, "target": 18}, {"source": 10, "target": 20}, {"source": 18, "target": 1}, {"source": 18, "target": 2}, {"source": 11, "target": 17}, {"source": 11, "target": 19}, {"source": 17, "target": 4}, {"source": 1, "target": 20}, {"source": 7, "target": 19}]} \ No newline at end of file diff --git a/qibojit-benchmarks/graphs/randomgraph_3_26.json b/qibojit-benchmarks/graphs/randomgraph_3_26.json new file mode 100644 index 0000000..a3457d5 --- /dev/null +++ b/qibojit-benchmarks/graphs/randomgraph_3_26.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 6}, {"id": 18}, {"id": 14}, {"id": 25}, {"id": 4}, {"id": 24}, {"id": 17}, {"id": 21}, {"id": 0}, {"id": 20}, {"id": 16}, {"id": 19}, {"id": 15}, {"id": 7}, {"id": 5}, {"id": 3}, {"id": 9}, {"id": 22}, {"id": 8}, {"id": 12}, {"id": 10}, {"id": 11}, {"id": 13}, {"id": 1}, {"id": 2}, {"id": 23}], "links": [{"source": 6, "target": 18}, {"source": 6, "target": 3}, {"source": 6, "target": 19}, {"source": 18, "target": 12}, {"source": 18, "target": 24}, {"source": 14, "target": 25}, {"source": 14, "target": 10}, {"source": 14, "target": 1}, {"source": 25, "target": 7}, {"source": 25, "target": 13}, {"source": 4, "target": 24}, {"source": 4, "target": 5}, {"source": 4, "target": 8}, {"source": 24, "target": 5}, {"source": 17, "target": 21}, {"source": 17, "target": 15}, {"source": 17, "target": 8}, {"source": 21, "target": 23}, {"source": 21, "target": 0}, {"source": 0, "target": 20}, {"source": 0, "target": 16}, {"source": 20, "target": 22}, {"source": 20, "target": 12}, {"source": 16, "target": 19}, {"source": 16, "target": 23}, {"source": 19, "target": 7}, {"source": 15, "target": 5}, {"source": 15, "target": 2}, {"source": 7, "target": 12}, {"source": 3, "target": 9}, {"source": 3, "target": 23}, {"source": 9, "target": 13}, {"source": 9, "target": 2}, {"source": 22, "target": 11}, {"source": 22, "target": 1}, {"source": 8, "target": 10}, {"source": 10, "target": 11}, {"source": 11, "target": 1}, {"source": 13, "target": 2}]} \ No newline at end of file diff --git a/qibojit-benchmarks/graphs/randomgraph_3_28.json b/qibojit-benchmarks/graphs/randomgraph_3_28.json new file mode 100644 index 0000000..e0db917 --- /dev/null +++ b/qibojit-benchmarks/graphs/randomgraph_3_28.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 24}, {"id": 27}, {"id": 6}, {"id": 15}, {"id": 18}, {"id": 20}, {"id": 12}, {"id": 16}, {"id": 21}, {"id": 22}, {"id": 19}, {"id": 11}, {"id": 14}, {"id": 0}, {"id": 17}, {"id": 8}, {"id": 13}, {"id": 9}, {"id": 23}, {"id": 3}, {"id": 5}, {"id": 7}, {"id": 25}, {"id": 4}, {"id": 1}, {"id": 2}, {"id": 10}, {"id": 26}], "links": [{"source": 24, "target": 27}, {"source": 24, "target": 8}, {"source": 24, "target": 23}, {"source": 27, "target": 19}, {"source": 27, "target": 5}, {"source": 6, "target": 15}, {"source": 6, "target": 5}, {"source": 6, "target": 25}, {"source": 15, "target": 20}, {"source": 15, "target": 5}, {"source": 18, "target": 20}, {"source": 18, "target": 21}, {"source": 18, "target": 7}, {"source": 20, "target": 19}, {"source": 12, "target": 16}, {"source": 12, "target": 19}, {"source": 12, "target": 11}, {"source": 16, "target": 9}, {"source": 16, "target": 2}, {"source": 21, "target": 22}, {"source": 21, "target": 0}, {"source": 22, "target": 13}, {"source": 22, "target": 17}, {"source": 11, "target": 14}, {"source": 11, "target": 4}, {"source": 14, "target": 13}, {"source": 14, "target": 26}, {"source": 0, "target": 17}, {"source": 0, "target": 1}, {"source": 17, "target": 1}, {"source": 8, "target": 10}, {"source": 8, "target": 13}, {"source": 9, "target": 23}, {"source": 9, "target": 3}, {"source": 23, "target": 10}, {"source": 3, "target": 26}, {"source": 3, "target": 2}, {"source": 7, "target": 25}, {"source": 7, "target": 4}, {"source": 25, "target": 26}, {"source": 4, "target": 1}, {"source": 2, "target": 10}]} \ No newline at end of file diff --git a/qibojit-benchmarks/graphs/randomgraph_3_30.json b/qibojit-benchmarks/graphs/randomgraph_3_30.json new file mode 100644 index 0000000..33c4d6f --- /dev/null +++ b/qibojit-benchmarks/graphs/randomgraph_3_30.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 6}, {"id": 18}, {"id": 7}, {"id": 17}, {"id": 3}, {"id": 4}, {"id": 20}, {"id": 24}, {"id": 29}, {"id": 5}, {"id": 13}, {"id": 23}, {"id": 28}, {"id": 0}, {"id": 9}, {"id": 26}, {"id": 19}, {"id": 1}, {"id": 12}, {"id": 10}, {"id": 15}, {"id": 16}, {"id": 25}, {"id": 11}, {"id": 21}, {"id": 27}, {"id": 8}, {"id": 14}, {"id": 2}, {"id": 22}], "links": [{"source": 6, "target": 18}, {"source": 6, "target": 24}, {"source": 6, "target": 26}, {"source": 18, "target": 29}, {"source": 18, "target": 16}, {"source": 7, "target": 17}, {"source": 7, "target": 20}, {"source": 7, "target": 14}, {"source": 17, "target": 0}, {"source": 17, "target": 19}, {"source": 3, "target": 4}, {"source": 3, "target": 12}, {"source": 3, "target": 11}, {"source": 4, "target": 11}, {"source": 4, "target": 23}, {"source": 20, "target": 9}, {"source": 20, "target": 28}, {"source": 24, "target": 19}, {"source": 24, "target": 0}, {"source": 29, "target": 8}, {"source": 29, "target": 21}, {"source": 5, "target": 13}, {"source": 5, "target": 0}, {"source": 5, "target": 21}, {"source": 13, "target": 26}, {"source": 13, "target": 10}, {"source": 23, "target": 28}, {"source": 23, "target": 19}, {"source": 28, "target": 15}, {"source": 9, "target": 26}, {"source": 9, "target": 16}, {"source": 1, "target": 12}, {"source": 1, "target": 2}, {"source": 1, "target": 22}, {"source": 12, "target": 14}, {"source": 10, "target": 15}, {"source": 10, "target": 2}, {"source": 15, "target": 2}, {"source": 16, "target": 25}, {"source": 25, "target": 11}, {"source": 25, "target": 27}, {"source": 21, "target": 27}, {"source": 27, "target": 22}, {"source": 8, "target": 14}, {"source": 8, "target": 22}]} \ No newline at end of file diff --git a/qibojit-benchmarks/graphs/randomgraph_3_4.json b/qibojit-benchmarks/graphs/randomgraph_3_4.json new file mode 100644 index 0000000..32cd3a3 --- /dev/null +++ b/qibojit-benchmarks/graphs/randomgraph_3_4.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 0}, {"id": 1}, {"id": 2}, {"id": 3}], "links": [{"source": 0, "target": 1}, {"source": 0, "target": 3}, {"source": 0, "target": 2}, {"source": 1, "target": 2}, {"source": 1, "target": 3}, {"source": 2, "target": 3}]} \ No newline at end of file diff --git a/qibojit-benchmarks/graphs/randomgraph_3_6.json b/qibojit-benchmarks/graphs/randomgraph_3_6.json new file mode 100644 index 0000000..01d9d28 --- /dev/null +++ b/qibojit-benchmarks/graphs/randomgraph_3_6.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 2}, {"id": 4}, {"id": 1}, {"id": 0}, {"id": 3}, {"id": 5}], "links": [{"source": 2, "target": 4}, {"source": 2, "target": 1}, {"source": 2, "target": 5}, {"source": 4, "target": 0}, {"source": 4, "target": 3}, {"source": 1, "target": 5}, {"source": 1, "target": 3}, {"source": 0, "target": 3}, {"source": 0, "target": 5}]} \ No newline at end of file diff --git a/qibojit-benchmarks/graphs/randomgraph_3_8.json b/qibojit-benchmarks/graphs/randomgraph_3_8.json new file mode 100644 index 0000000..0ed879c --- /dev/null +++ b/qibojit-benchmarks/graphs/randomgraph_3_8.json @@ -0,0 +1 @@ +{"directed": false, "multigraph": false, "graph": {}, "nodes": [{"id": 0}, {"id": 7}, {"id": 1}, {"id": 2}, {"id": 4}, {"id": 3}, {"id": 5}, {"id": 6}], "links": [{"source": 0, "target": 7}, {"source": 0, "target": 4}, {"source": 0, "target": 3}, {"source": 7, "target": 1}, {"source": 7, "target": 4}, {"source": 1, "target": 2}, {"source": 1, "target": 6}, {"source": 2, "target": 6}, {"source": 2, "target": 5}, {"source": 4, "target": 5}, {"source": 3, "target": 6}, {"source": 3, "target": 5}]} \ No newline at end of file diff --git a/qibojit-benchmarks/main.py b/qibojit-benchmarks/main.py new file mode 100644 index 0000000..8713513 --- /dev/null +++ b/qibojit-benchmarks/main.py @@ -0,0 +1,59 @@ +"""Launches the circuit benchmark script for user given arguments.""" +import argparse +from benchmarks.scripts import circuit_benchmark + + +THREADING_LINK = "https://numba.pydata.org/numba-doc/latest/user/threading-layer.html#selecting-a-named-threading-layer" + +parser = argparse.ArgumentParser() +parser.add_argument("--nqubits", default=20, type=int, + help="Number of qubits in the circuit.") +parser.add_argument("--backend", default="qibojit", type=str, + help="Qibo backend to use for simulation.") +parser.add_argument("--platform", default=None, type=str, + help="Qibo platform to use for simulation.") + +parser.add_argument("--circuit", default="qft", type=str, + help="Type of circuit to use. See README for the list of " + "available circuits.") +parser.add_argument("--circuit-options", default=None, type=str, + help="String with options for circuit creation. " + "It should have the form 'arg1=value1,arg2=value2,...'. " + "See README for the list of arguments that are " + "available for each circuit.") + +parser.add_argument("--nreps", default=1, type=int, + help="Number of repetitions of the circuit execution. " + "Dry run is not included.") +parser.add_argument("--nshots", default=None, type=int, + help="Number of measurement shots. If used the time " + "required to measure frequencies (no samples) is " + "measured and logged. If it is ``None`` no " + "measurements are performed.") +parser.add_argument("--transfer", action="store_true", + help="If used the final state array is converted to numpy. " + "If the simulation device is GPU this requires a " + "transfer from GPU memory to CPU.") + +parser.add_argument("--precision", default="double", type=str, + help="Numerical precision of the simulation. " + "Choose between 'double' and 'single'.") +parser.add_argument("--memory", default=None, type=int, + help="Limit the GPU memory usage when using Tensorflow " + "based backends. The memory limit should be given " + "in MB. Tensorflow reserves the full available memory " + "by default.") +parser.add_argument("--threading", default=None, type=str, + help="Switches the numba threading layer when using the " + "qibojit backend on CPU. See {} for a list of " + "available threading layers.".format(THREADING_LINK)) + +parser.add_argument("--filename", default=None, type=str, + help="Directory of file to save the logs in json format. " + "If not given the logs will only be printed and not saved.") + + +if __name__ == "__main__": + args = vars(parser.parse_args()) + args["circuit_name"] = args.pop("circuit") + circuit_benchmark(**args) diff --git a/qibojit-benchmarks/plot_pathfinding_metric.py b/qibojit-benchmarks/plot_pathfinding_metric.py new file mode 100644 index 0000000..3c433c8 --- /dev/null +++ b/qibojit-benchmarks/plot_pathfinding_metric.py @@ -0,0 +1,480 @@ +from plots.utils import load_data + +import matplotlib +import matplotlib.pyplot as plt +import seaborn as sns +from matplotlib.patches import Patch +matplotlib.rcParams['mathtext.fontset'] = 'cm' +matplotlib.rcParams['font.family'] = 'STIXGeneral' +import pandas as pd + +save = True # if ``True`` plots will be saved in the current directory as pdfs + + +def plot_scaling_expectation(input1, input2, input3, circuit, quantity, precision="double", fontsize=30, legend=True, save=False): + + + combine_data = input1 + combine_data2 = input2 + combine_data3 = input3 + + matplotlib.rcParams["font.size"] = fontsize + # Prepare GPU data + condition = (combine_data["circuit"] == circuit) & (combine_data["precision"] == precision) + condition2 = (combine_data2["circuit"] == circuit) & (combine_data2["precision"] == precision) + condition3 = (combine_data3["circuit"] == circuit) & (combine_data3["precision"] == precision) + + data = {} + data["qibotn MPI"] = combine_data[(combine_data["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json") & condition] + data["qibotn NCCL"] = combine_data[(combine_data["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_nccl_expectation.json") & condition] + data["qibotn"] = combine_data[(combine_data["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_expectation.json") & condition] + data["qibojit numba"] = combine_data[(combine_data["library_options"] == "backend=qibojit,platform=numba,expectation=XXXZ") & condition] + + data2 = {} + data2["qibotn MPI"] = combine_data2[(combine_data2["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json") & condition2] + data2["qibotn NCCL"] = combine_data2[(combine_data2["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_nccl_expectation.json") & condition2] + data2["qibotn"] = combine_data2[(combine_data2["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_expectation.json") & condition2] + data2["qibojit numba"] = combine_data2[(combine_data2["library_options"] == "backend=qibojit,platform=numba,expectation=XXXZ") & condition2] + + data3 = {} + data3["qibotn MPI"] = combine_data3[(combine_data3["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json") & condition3] + data3["qibotn NCCL"] = combine_data3[(combine_data3["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_nccl_expectation.json") & condition3] + data3["qibotn"] = combine_data3[(combine_data3["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_expectation.json") & condition3] + data3["qibojit numba"] = combine_data3[(combine_data3["library_options"] == "backend=qibojit,platform=numba,expectation=XXXZ") & condition3] + + # Plot data + cpu_cp = sns.color_palette("Oranges", 7) + gpu_cp = sns.color_palette("Purples", 7) + gpu_cp2 = sns.color_palette("Greens", 7) + gpu_cp3 = sns.color_palette("Reds", 7) + + plt.figure(figsize=(16, 9)) + + + plt.semilogy(data["qibotn"]["nqubits"], data["qibotn"][quantity], + color=cpu_cp[3], linewidth=1.5, label="1x1 gpu", marker=".", markersize=10) + plt.semilogy(data["qibotn MPI"]["nqubits"], data["qibotn MPI"][quantity], + color=gpu_cp[3], linewidth=1.5, label="1x8 gpu MPI", marker=".", markersize=10) + # plt.semilogy(data["qibotn NCCL"]["nqubits"], data["qibotn NCCL"][quantity], + # color=gpu_cp[5], linewidth=1.5, label="1x8 gpu NCCL", marker=".", markersize=10) + + plt.semilogy(data2["qibotn MPI"]["nqubits"], data2["qibotn MPI"][quantity], + color=gpu_cp2[3], linewidth=1.5, label="2x8 gpu MPI", marker=".", markersize=10) + plt.semilogy(data3["qibotn MPI"]["nqubits"], data3["qibotn MPI"][quantity], + color=gpu_cp3[3], linewidth=1.5, label="4x8 gpu MPI", marker=".", markersize=10) + + # plt.semilogy(data["qibojit numba"]["nqubits"], data["qibojit numba"][quantity], + # color=cpu_cp[2], linewidth=1.5, label="CPU", marker=".", markersize=10) + + # plt.ylim(bottom=1e-4, top=1e3) + # plt.xlim(left=0, right=1750) + # plt.xlim(left=0, right=60) + + #plt.xlim(right=31) + + plt.title(f"Expectation: {circuit}, depth {5}, {precision} precision") + plt.xlabel("Number of qubits") + if quantity == "total_dry_time": + plt.ylabel("Total dry run time (sec)") + elif quantity == "total_simulation_time": + plt.ylabel("Total simulation time (sec)") + elif quantity == "simulation_times_mean": + plt.ylabel("Simulation times mean (sec)") + if legend: + plt.legend(fontsize="small") + if save: + plt.savefig(f"qibo_scaling_{circuit}_{quantity}_{precision}_ex.pdf", bbox_inches="tight") + else: + plt.show() + +def plot_pathfinding_data(input1, input2, input3, circuit, quantity, precision="double", fontsize=30, legend=True, save=False): + num_reps=3 + num_pathfinding_per_rep=4 #equivalent to total number of GPUs + combine_data = input1 + combine_data2 = input2 + combine_data3 = input3 + + matplotlib.rcParams["font.size"] = fontsize + # Prepare GPU data + condition = (combine_data["circuit"] == circuit) & (combine_data["precision"] == precision) + condition2 = (combine_data2["circuit"] == circuit) & (combine_data2["precision"] == precision) + condition3 = (combine_data3["circuit"] == circuit) & (combine_data3["precision"] == precision) + + data = {} + data["qibotn MPI"] = combine_data[(combine_data["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json") & condition] + data2 = {} + data2["qibotn MPI"] = combine_data2[(combine_data2["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json") & condition2] + data3 = {} + data3["qibotn MPI"] = combine_data3[(combine_data3["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json") & condition3] + + # Plot data + + color_1x1 = sns.color_palette("Oranges", 7) + color_1x4 = sns.color_palette("Purples", 7) + color_2x4 = sns.color_palette("Greens", 7) + color_4x4 = sns.color_palette("Reds", 7) + + def extract_metric(data,rep_index,metric_of_interest): + #metric of interest 0: num slices, 1: opt cost, 2: largest intermediate, 3: time + metric_list_total=[] + metric_max=[] + metric_min=[] + metric_max_index=[] + metric_min_index=[] + for each_record in range(len(data["qibotn MPI"]['nqubits'])): + each_rep = data["qibotn MPI"][quantity][each_record][rep_index][0] + split_data = [each_rep[i:i + 4] for i in range(0, len(each_rep), 4)] + metric_list=[] + for each_gpu in range(len(split_data)): + metric_list.append(split_data[each_gpu][metric_of_interest]) + metric_list_total.append(metric_list) + max_value = max(metric_list) + max_value_index = metric_list.index(max_value) + min_value = min(metric_list) + min_value_index = metric_list.index(min_value) + metric_max.append(max_value) + metric_min.append(min_value) + metric_max_index.append(max_value_index) + metric_min_index.append(min_value_index) + + return metric_list_total, metric_max, metric_min, metric_max_index, metric_min_index + + def extract_metric_with_index_of_interest(data,rep_index,metric_of_interest,index_of_interest): + #metric of interest 0: num slices, 1: opt cost, 2: largest intermediate, 3: time + metric_list_total=[] + metric_max=[] + metric_min=[] + metric_selected=[] + + for each_record in range(len(data["qibotn MPI"]['nqubits'])): + each_rep = data["qibotn MPI"][quantity][each_record][rep_index][0] + split_data = [each_rep[i:i + 4] for i in range(0, len(each_rep), 4)] + metric_list=[] + for each_gpu in range(len(split_data)): + metric_list.append(split_data[each_gpu][metric_of_interest]) + metric_list_total.append(metric_list) + max_value = max(metric_list) + interest_value = metric_list[(index_of_interest[each_record])] + min_value = min(metric_list) + + metric_max.append(max_value) + metric_min.append(min_value) + metric_selected.append(interest_value) + + return metric_list_total, metric_max, metric_min, metric_selected + + rep_data = 0 + rep_data2 = 1 + rep_data3 = 0 + + data_opt_cost_original, max_cost, min_cost,max_cost_index, min_cost_index = extract_metric(data,rep_data,1) + data_opt_cost = [list(i) for i in zip(*data_opt_cost_original)] + + data_opt_cost_original2, max_cost2, min_cost2, max_cost_index2, min_cost_index2 = extract_metric(data2,rep_data2,1) + data_opt_cost2 = [list(i) for i in zip(*data_opt_cost_original2)] + + data_opt_cost_original3, max_cost3, min_cost3, max_cost_index3, min_cost_index3 = extract_metric(data3,rep_data3,1) + data_opt_cost3 = [list(i) for i in zip(*data_opt_cost_original3)] + + ##### + data_num_slices_original, _, _,selected1 = extract_metric_with_index_of_interest(data,rep_data,0,min_cost_index) + data_num_slices = [list(i) for i in zip(*data_num_slices_original)] + + data_num_slices_original2, _, _, selected2= extract_metric_with_index_of_interest(data2,rep_data2,0,min_cost_index2) + data_num_slices2 = [list(i) for i in zip(*data_num_slices_original2)] + + data_num_slices_original3, _, _, selected3 = extract_metric_with_index_of_interest(data3,rep_data3,0,min_cost_index3) + data_num_slices3 = [list(i) for i in zip(*data_num_slices_original3)] + + ####_time + data_time_original, max_cost_time, min_cost_time,max_cost_index_time, min_cost_index_time = extract_metric(data,rep_data,3) + data_time = [list(i) for i in zip(*data_time_original)] + + data_time_original2, max_cost2_time, min_cost2_time, max_cost_index2_time, min_cost_index2_time = extract_metric(data2,rep_data2,3) + data_time2 = [list(i) for i in zip(*data_time_original2)] + + data_time_original3, max_cost3_time, min_cost3_time, max_cost_index3_time, min_cost_index3_time = extract_metric(data3,rep_data3,3) + data_time3 = [list(i) for i in zip(*data_time_original3)] + + # for i in range(len(data_opt_cost)): + # print("max", max_cost[i],"min",min_cost[i],data_opt_cost[i]) + # print("1x4",data["qibotn MPI"]["nqubits"]) + # print("2x4",data2["qibotn MPI"]["nqubits"]) + # print("4x4",data3["qibotn MPI"]["nqubits"]) + + + #### PLOT COST OF PATH #### + plt.figure(figsize=(16, 9)) + + color4x4=3 + # 4x4 + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[0], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[1], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[2], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[3], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[4], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[5], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[6], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[7], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[8], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[9], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[10], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[11], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[12], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[13], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[14], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_opt_cost3[15], + color=color_4x4[color4x4], marker=".", s=100) + + # 2x4 + plt.scatter(data2["qibotn MPI"]["nqubits"], data_opt_cost2[0], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_opt_cost2[1], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_opt_cost2[2], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_opt_cost2[3], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_opt_cost2[4], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_opt_cost2[5], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_opt_cost2[6], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_opt_cost2[7], + color=color_2x4[2], marker=".", s=100) + + + + # 1x4 + plt.scatter(data["qibotn MPI"]["nqubits"], data_opt_cost[0], + color=color_1x4[2], marker=".", s=100) + plt.scatter(data["qibotn MPI"]["nqubits"], data_opt_cost[1], + color=color_1x4[2], marker=".", s=100) + plt.scatter(data["qibotn MPI"]["nqubits"], data_opt_cost[2], + color=color_1x4[2], marker=".", s=100) + plt.scatter(data["qibotn MPI"]["nqubits"], data_opt_cost[3], + color=color_1x4[2],marker=".", s=100) + + # Best path + plt.semilogy(data["qibotn MPI"]["nqubits"], min_cost, + color=color_1x4[3], label="1x4 selected", linewidth=1.5, marker="x", markersize=10) + # Best path + plt.semilogy(data2["qibotn MPI"]["nqubits"], min_cost2, + color=color_2x4[3], label="2x4 selected",linewidth=1.5, marker="x", markersize=10) + # Best path + plt.semilogy(data3["qibotn MPI"]["nqubits"], min_cost3, + color=color_4x4[3], label="4x4 selected",linewidth=1.5, marker="x", markersize=10) + + plt.xlim(left=0, right=305) + + plt.title(f"Cost of Paths Found: {circuit}, depth {5}, {precision} precision") + plt.xlabel("Number of qubits") + plt.ylabel("FLOP") + if legend: + plt.legend(fontsize="small") + if save: + plt.savefig(f"qibo_scaling_{circuit}_{quantity}_{precision}_pathfinding.pdf", bbox_inches="tight") + else: + plt.show() + +#### PLOT NUM OF SLICES #### + plt.figure(figsize=(16, 9)) + + color4x4=3 + # 4x4 + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[0], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[1], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[2], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[3], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[4], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[5], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[6], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[7], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[8], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[9], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[10], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[11], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[12], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[13], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[14], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_num_slices3[15], + color=color_4x4[color4x4], marker=".", s=100) + + + # 2x4 + plt.scatter(data2["qibotn MPI"]["nqubits"], data_num_slices2[0], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_num_slices2[1], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_num_slices2[2], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_num_slices2[3], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_num_slices2[4], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_num_slices2[5], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_num_slices2[6], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_num_slices2[7], + color=color_2x4[2], marker=".", s=100) + + + # 1x4 + plt.scatter(data["qibotn MPI"]["nqubits"], data_num_slices[0], + color=color_1x4[2], marker=".", s=100) + plt.scatter(data["qibotn MPI"]["nqubits"], data_num_slices[1], + color=color_1x4[2], marker=".", s=100) + plt.scatter(data["qibotn MPI"]["nqubits"], data_num_slices[2], + color=color_1x4[2], marker=".", s=100) + plt.scatter(data["qibotn MPI"]["nqubits"], data_num_slices[3], + color=color_1x4[2],marker=".", s=100) + + # Best path + plt.semilogy(data["qibotn MPI"]["nqubits"], selected1, + color=color_1x4[3], label="1x4 selected", linewidth=1.5, marker="x", markersize=10) + # Best path + plt.semilogy(data2["qibotn MPI"]["nqubits"], selected2, + color=color_2x4[3], label="2x4 selected",linewidth=1.5, marker="x", markersize=10) + # Best path + plt.semilogy(data3["qibotn MPI"]["nqubits"], selected3, + color=color_4x4[3], label="4x4 selected",linewidth=1.5, marker="x", markersize=10) + + plt.xlim(left=0, right=305) + + plt.title(f"Slicing: {circuit}, depth {5}, {precision} precision") + plt.xlabel("Number of qubits") + plt.ylabel("Number of slices") + if legend: + plt.legend(fontsize="small") + if save: + plt.savefig(f"qibo_scaling_{circuit}_{quantity}_{precision}_slicing.pdf", bbox_inches="tight") + else: + plt.show() + +#### PLOT Time PF #### + plt.figure(figsize=(16, 9)) + + color4x4=3 + # 4x4 + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[0], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[1], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[2], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[3], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[4], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[5], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[6], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[7], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[8], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[9], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[10], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[11], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[12], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[13], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[14], + color=color_4x4[color4x4], marker=".", s=100) + plt.scatter(data3["qibotn MPI"]["nqubits"], data_time3[15], + color=color_4x4[color4x4], marker=".", s=100) + + + # 2x4 + plt.scatter(data2["qibotn MPI"]["nqubits"], data_time2[0], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_time2[1], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_time2[2], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_time2[3], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_time2[4], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_time2[5], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_time2[6], + color=color_2x4[2], marker=".", s=100) + plt.scatter(data2["qibotn MPI"]["nqubits"], data_time2[7], + color=color_2x4[2], marker=".", s=100) + + + # 1x4 + plt.scatter(data["qibotn MPI"]["nqubits"], data_time[0], + color=color_1x4[2], marker=".", s=100) + plt.scatter(data["qibotn MPI"]["nqubits"], data_time[1], + color=color_1x4[2], marker=".", s=100) + plt.scatter(data["qibotn MPI"]["nqubits"], data_time[2], + color=color_1x4[2], marker=".", s=100) + plt.scatter(data["qibotn MPI"]["nqubits"], data_time[3], + color=color_1x4[2],marker=".", s=100) + + # Best path + plt.semilogy(data["qibotn MPI"]["nqubits"], max_cost_time, + color=color_1x4[3], label="1x4 selected", linewidth=1.5, marker="x", markersize=10) + # Best path + plt.semilogy(data2["qibotn MPI"]["nqubits"], max_cost2_time, + color=color_2x4[3], label="2x4 selected",linewidth=1.5, marker="x", markersize=10) + # Best path + plt.semilogy(data3["qibotn MPI"]["nqubits"], max_cost3_time, + color=color_4x4[3], label="4x4 selected",linewidth=1.5, marker="x", markersize=10) + + plt.xlim(left=0, right=305) + + plt.title(f"Pathfinding Time: {circuit}, depth {5}, {precision} precision") + plt.xlabel("Number of qubits") + plt.ylabel("Pathfinding Time (sec)") + if legend: + plt.legend(fontsize="small") + if save: + plt.savefig(f"qibo_scaling_{circuit}_{quantity}_{precision}_pftime.pdf", bbox_inches="tight") + else: + plt.show() + +data1 = load_data("/home/project/11003124/tankya/costa1/qibojit-benchmarks/qibotn_pathfind_double_1x4_variational_d5.dat") +data2 = load_data("/home/project/11003124/tankya/costa1/qibojit-benchmarks/qibotn_pathfind_double_2x4_variational_d5.dat") +data3 = load_data("/home/project/11003124/tankya/costa1/qibojit-benchmarks/qibotn_pathfind_double_4x4_variational_d5_old.dat") + +plot_pathfinding_data(data1,data2,data3,"variational", "expectation_result","double", legend=True, save=save) diff --git a/qibojit-benchmarks/plot_result_quad.py b/qibojit-benchmarks/plot_result_quad.py new file mode 100644 index 0000000..cfcdf61 --- /dev/null +++ b/qibojit-benchmarks/plot_result_quad.py @@ -0,0 +1,135 @@ +from plots.utils import load_data + +import matplotlib +import matplotlib.pyplot as plt +import seaborn as sns +from matplotlib.patches import Patch +matplotlib.rcParams['mathtext.fontset'] = 'cm' +matplotlib.rcParams['font.family'] = 'STIXGeneral' +import pandas as pd + +save = True # if ``True`` plots will be saved in the current directory as pdfs + + +def plot_scaling_expectation(input1, input2, input3,input4, circuit, quantity, precision="double", fontsize=30, legend=True, save=False): + + + combine_data = input1 + combine_data2 = input2 + combine_data3 = input3 + combine_data4 = input4 + + matplotlib.rcParams["font.size"] = fontsize + # Prepare GPU data + condition = (combine_data["circuit"] == circuit) & (combine_data["precision"] == precision) + condition2 = (combine_data2["circuit"] == circuit) & (combine_data2["precision"] == precision) + condition3 = (combine_data3["circuit"] == circuit) & (combine_data3["precision"] == precision) + condition4 = (combine_data4["circuit"] == circuit) & (combine_data4["precision"] == precision) + + data = {} + data["qibotn MPI"] = combine_data[(combine_data["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json") & condition] + data["qibotn NCCL"] = combine_data[(combine_data["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_nccl_expectation.json") & condition] + data["qibotn"] = combine_data[(combine_data["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_expectation.json") & condition] + data["qibojit numba"] = combine_data[(combine_data["library_options"] == "backend=qibojit,platform=numba,expectation=XXXZ") & condition] + + data2 = {} + data2["qibotn MPI"] = combine_data2[(combine_data2["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json") & condition2] + data2["qibotn NCCL"] = combine_data2[(combine_data2["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_nccl_expectation.json") & condition2] + data2["qibotn"] = combine_data2[(combine_data2["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_expectation.json") & condition2] + data2["qibojit numba"] = combine_data2[(combine_data2["library_options"] == "backend=qibojit,platform=numba,expectation=XXXZ") & condition2] + + data3 = {} + data3["qibotn MPI"] = combine_data3[(combine_data3["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json") & condition3] + data3["qibotn NCCL"] = combine_data3[(combine_data3["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_nccl_expectation.json") & condition3] + data3["qibotn"] = combine_data3[(combine_data3["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_expectation.json") & condition3] + data3["qibojit numba"] = combine_data3[(combine_data3["library_options"] == "backend=qibojit,platform=numba,expectation=XXXZ") & condition3] + + data4 = {} + data4["qibotn MPI"] = combine_data4[(combine_data4["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json") & condition4] + data4["qibotn NCCL"] = combine_data4[(combine_data4["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_nccl_expectation.json") & condition4] + data4["qibotn"] = combine_data4[(combine_data4["library_options"] == "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_expectation.json") & condition4] + data4["qibojit numba"] = combine_data4[(combine_data4["library_options"] == "backend=qibojit,platform=numba,expectation=XXXZ") & condition4] + + # Plot data + cpu_cp = sns.color_palette("Oranges", 7) + gpu_cp = sns.color_palette("Purples", 7) + gpu_cp2 = sns.color_palette("Greens", 7) + gpu_cp3 = sns.color_palette("Reds", 7) + gpu_cp4 = sns.color_palette("Blues", 7) + plt.figure(figsize=(16, 9)) + + # 24 hours in seconds + seconds_in_24_hours = 24 * 60 * 60 + + # Plotting the horizontal dashed line + plt.axhline(y=seconds_in_24_hours, color='r', linestyle='--', linewidth=1.5, label='24 hours') + + plt.semilogy(data["qibotn"]["nqubits"], data["qibotn"][quantity], + color=cpu_cp[3], linewidth=1.5, label="1x1 gpu", marker=".", markersize=10) + plt.semilogy(data2["qibotn MPI"]["nqubits"], data2["qibotn MPI"][quantity], + color=gpu_cp[3], linewidth=1.5, label="1x4 gpu MPI", marker=".", markersize=10) + # plt.semilogy(data["qibotn NCCL"]["nqubits"], data["qibotn NCCL"][quantity], + # color=gpu_cp[5], linewidth=1.5, label="1x8 gpu NCCL", marker=".", markersize=10) + + plt.semilogy(data3["qibotn MPI"]["nqubits"], data3["qibotn MPI"][quantity], + color=gpu_cp2[3], linewidth=1.5, label="2x4 gpu MPI", marker=".", markersize=10) + plt.semilogy(data4["qibotn MPI"]["nqubits"], data4["qibotn MPI"][quantity], + color=gpu_cp3[3], linewidth=1.5, label="4x4 gpu MPI", marker=".", markersize=10) + # plt.semilogy(data4["qibotn MPI"]["nqubits"], data4["qibotn MPI"][quantity], + # color=gpu_cp5[3], linewidth=1.5, label="4x8 gpu MPI", marker=".", markersize=10) + + # plt.semilogy(data["qibojit numba"]["nqubits"], data["qibojit numba"][quantity], + # color=cpu_cp[2], linewidth=1.5, label="CPU", marker=".", markersize=10) + + # plt.ylim(bottom=1e-4, top=1e3) + # plt.xlim(left=0, right=1750) + # plt.xlim(left=0, right=60) + + #plt.xlim(right=31) + + plt.title(f"Expectation: {circuit}, depth {5}, {precision} precision") + plt.xlabel("Number of qubits") + if quantity == "total_dry_time": + plt.ylabel("Total dry run time (sec)") + elif quantity == "total_simulation_time": + plt.ylabel("Total simulation time (sec)") + elif quantity == "simulation_times_mean": + plt.ylabel("Simulation times mean (sec)") + if legend: + plt.legend(fontsize="small") + if save: + plt.savefig(f"qibo_scaling_{circuit}_{quantity}_{precision}_ex.pdf", bbox_inches="tight") + else: + plt.show() + + +# data1 = load_data("/home/user3/qibotn_benchmarks/qibojit-benchmarks/qibotn_expectation_double_1x8_var.dat") +# plot_scaling_expectation(data1,"variational", "simulation_times_mean","double", legend=True, save=save) + +# data1 = load_data("/home/user3/qibotn_benchmarks/qibojit-benchmarks/qibotn_expectation_double_1x8_sup.dat") +# plot_scaling_expectation(data1,"supremacy", "simulation_times_mean","double", legend=True, save=save) + +# data1 = load_data("/home/user3/qibotn_benchmarks/qibojit-benchmarks/qibotn_expectation_double_1x8_qft.dat") +# data1 = load_data("/home/user3/qibotn_benchmarks/qibojit-benchmarks/qibotn_expectation_double_1x8_sup.dat") +# data1 = load_data("/home/user3/qibotn_benchmarks/qibojit-benchmarks/qibotn_expectation_double_1x8_var.dat") +data1 = load_data("/home/project/11003124/tankya/costa1/qibojit-benchmarks/qibotn_expectation_double_1x1_variational_d5.dat") +data2 = load_data("/home/project/11003124/tankya/costa1/qibojit-benchmarks/qibotn_expectation_double_1x4_variational_d5.dat") +data3 = load_data("/home/project/11003124/tankya/costa1/qibojit-benchmarks/qibotn_expectation_double_2x4_variational_d5.dat") +data4 = load_data("/home/project/11003124/tankya/costa1/qibojit-benchmarks/qibotn_expectation_double_4x4_variational_d5.dat") + +plot_scaling_expectation(data1,data2,data3,data4,"variational", "simulation_times_mean","double", legend=True, save=save) + +#1x4 Missing value + +{"datetime": "2024-07-13 01:38:18", "nqubits": 10, "nreps": 1, "import_time": 2.0695817470550537, "library_options": "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json", "library": "qibo", "precision": "double", "device": "/CPU:0", "version": "0.2.5", "circuit": "variational", "circuit_options": "nqubits=10, nlayers=5, seed=123", "creation_time": 0.12717008590698242, "dry_run_time": 4.76837158203125e-07, "dtype": "complex128", "simulation_times": [2.722426176071167], "simulation_times_mean": 2.722426176071167, "simulation_times_std": 0.0, "expectation_result": [[[0.044218473681389266]]], "expectation_result_mean": 0.044218473681389266, "expectation_result_std": 0.0} + +{"datetime": "2024-07-13 01:38:18", "nqubits": 40, "nreps": 1, "import_time": 2.0695817470550537, "library_options": "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json", "library": "qibo", "precision": "double", "device": "/CPU:0", "version": "0.2.5", "circuit": "variational", "circuit_options": "nqubits=40, nlayers=5, seed=123", "creation_time": 0.12717008590698242, "dry_run_time": 4.76837158203125e-07, "dtype": "complex128", "simulation_times": [92.54630327224731], "simulation_times_mean": 92.54630327224731, "simulation_times_std": 0.0, "expectation_result": [[[1.0185800644540888e-06]]], "expectation_result_mean": 1.0185800644540888e-06, "expectation_result_std": 0.0} + +{"datetime": "2024-07-13 01:38:18", "nqubits": 60, "nreps": 1, "import_time": 2.0695817470550537, "library_options": "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json", "library": "qibo", "precision": "double", "device": "/CPU:0", "version": "0.2.5", "circuit": "variational", "circuit_options": "nqubits=60, nlayers=5, seed=123", "creation_time": 0.12717008590698242, "dry_run_time": 4.76837158203125e-07, "dtype": "complex128", "simulation_times": [271.3857560157776], "simulation_times_mean": 271.3857560157776, "simulation_times_std": 0.0, "expectation_result": [[[5.877344374846953e-11]]], "expectation_result_mean": 5.877344374846953e-11, "expectation_result_std": 0.0} + +{"datetime": "2024-07-13 01:38:18", "nqubits": 80, "nreps": 1, "import_time": 2.0695817470550537, "library_options": "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json", "library": "qibo", "precision": "double", "device": "/CPU:0", "version": "0.2.5", "circuit": "variational", "circuit_options": "nqubits=80, nlayers=5, seed=123", "creation_time": 0.12717008590698242, "dry_run_time": 4.76837158203125e-07, "dtype": "complex128", "simulation_times": [1084.1304409503937], "simulation_times_mean": 1084.1304409503937, "simulation_times_std": 0.0, "expectation_result": [[[5.36328554751669e-16]]], "expectation_result_mean": 5.36328554751669e-16, "expectation_result_std": 0.0} + +{"datetime": "2024-07-13 01:38:18", "nqubits": 110, "nreps": 1, "import_time": 2.0695817470550537, "library_options": "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json", "library": "qibo", "precision": "double", "device": "/CPU:0", "version": "0.2.5", "circuit": "variational", "circuit_options": "nqubits=110, nlayers=5, seed=123", "creation_time": 0.12717008590698242, "dry_run_time": 4.76837158203125e-07, "dtype": "complex128", "simulation_times": [9504.664041519165], "simulation_times_mean": 9504.664041519165, "simulation_times_std": 0.0, "expectation_result": [[[3.446458226795824e-19]]], "expectation_result_mean": 3.446458226795824e-19, "expectation_result_std": 0.0} + +{"datetime": "2024-07-13 01:38:18", "nqubits": 130, "nreps": 1, "import_time": 2.0695817470550537, "library_options": "backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json", "library": "qibo", "precision": "double", "device": "/CPU:0", "version": "0.2.5", "circuit": "variational", "circuit_options": "nqubits=130, nlayers=5, seed=123", "creation_time": 0.12717008590698242, "dry_run_time": 4.76837158203125e-07, "dtype": "complex128", "simulation_times": [14543.339746236801], "simulation_times_mean": 14543.339746236801, "simulation_times_std": 0.0, "expectation_result": [[[1.789240796703424e-20]]], "expectation_result_mean": 1.789240796703424e-20, "expectation_result_std": 0.0} + diff --git a/qibojit-benchmarks/plots/__init__.py b/qibojit-benchmarks/plots/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/qibojit-benchmarks/plots/barplots.py b/qibojit-benchmarks/plots/barplots.py new file mode 100644 index 0000000..18b7fb2 --- /dev/null +++ b/qibojit-benchmarks/plots/barplots.py @@ -0,0 +1,249 @@ +"""Generates bar plots for qibojit breakdowns and multigpu comparisons.""" +import numpy as np +import matplotlib +import matplotlib.pyplot as plt +import seaborn as sns +from matplotlib.patches import Patch +matplotlib.rcParams['mathtext.fontset'] = 'cm' +matplotlib.rcParams['font.family'] = 'STIXGeneral' + + +def plot_breakdown_nqubits(data, circuit, precision="double", width=0.1, fontsize=30, save=False): + """Creates dry run vs simulation barplot with import time breakdown for given circuit varying the number of qubits.""" + matplotlib.rcParams["font.size"] = fontsize + + # Set plot params + hatches = ['/', '\\', 'o', '-', 'x', '.', '*'] + quantities = ["import_time", "creation_time", "dry_run_time", "simulation_times_mean"] + + nqubits = [22, 24, 26, 28, 30] + widths = [-5 * width / 2, - 3 * width / 2, -width / 2, width / 2, 3 * width / 2, 5 * width / 2] + oranges = sns.color_palette("Oranges", 2) + purples = sns.color_palette("Purples", 2) + greens = sns.color_palette("Greens", 2) + greys = sns.color_palette("Greys", 2) + + # Plot the results + plt.figure(figsize=(25, 9)) + plt.title(f"qibojit - Dry run vs simulation - {circuit}") + + xvalues = np.array(range(len(nqubits))) + plt.xticks(xvalues, nqubits) + + base_condition = ((data["precision"] == precision) & (data["circuit"] == circuit)) + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=numba") + heights_numba = {q: np.array([float(data[condition & (data["nqubits"] == n)][q]) for n in nqubits]) + for q in quantities} + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=cupy") + heights_cupy = {q: np.array([float(data[condition & (data["nqubits"] == n)][q]) for n in nqubits]) + for q in quantities} + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=cuquantum") + heights_cuquantum = {q: np.array([float(data[condition & (data["nqubits"] == n)][q]) for n in nqubits]) + for q in quantities} + + plt.bar(xvalues + widths[0], heights_numba["import_time"], + color=greys[0], align="center", width=width, alpha=1, hatch=hatches[0], + edgecolor='w') + plt.bar(xvalues + widths[1], heights_numba["import_time"], + color=greys[1], align="center", width=width, alpha=1, hatch=hatches[1], + edgecolor='w') + + plt.bar(xvalues + widths[2], heights_cupy["import_time"], + color=greys[0], align="center", width=width, alpha=1, hatch=hatches[0], + edgecolor='w') + plt.bar(xvalues + widths[3], heights_cupy["import_time"], + color=greys[1], align="center", width=width, alpha=1, hatch=hatches[1], + edgecolor='w') + + plt.bar(xvalues + widths[4], heights_cuquantum["import_time"], + color=greys[0], align="center", width=width, alpha=1, hatch=hatches[0], + edgecolor='w') + plt.bar(xvalues + widths[5], heights_cuquantum["import_time"], + color=greys[1], align="center", width=width, alpha=1, hatch=hatches[1], + edgecolor='w') + + plt.bar(xvalues + widths[0], heights_numba["dry_run_time"], + color=oranges[0], align="center", width=width, alpha=1, hatch=hatches[0], + edgecolor='w', bottom=heights_numba["import_time"] + heights_numba["creation_time"]) + plt.bar(xvalues + widths[1], heights_numba["simulation_times_mean"], + color=oranges[1], align="center", width=width, alpha=1, hatch=hatches[1], + edgecolor='w', bottom=heights_numba["import_time"] + heights_numba["creation_time"]) + + plt.bar(xvalues + widths[2], heights_cupy["dry_run_time"], + color=purples[0], align="center", width=width, alpha=1, hatch=hatches[0], + edgecolor='w', bottom=heights_cupy["import_time"] + heights_cupy["creation_time"]) + plt.bar(xvalues + widths[3], heights_cupy["simulation_times_mean"], + color=purples[1], align="center", width=width, alpha=1, hatch=hatches[1], + edgecolor='w', bottom=heights_cupy["import_time"] + heights_cupy["creation_time"]) + + plt.bar(xvalues + widths[4], heights_cuquantum["dry_run_time"], + color=greens[0], align="center", width=width, alpha=1, hatch=hatches[0], + edgecolor='w', bottom=heights_cuquantum["import_time"] + heights_cuquantum["creation_time"]) + plt.bar(xvalues + widths[5], heights_cuquantum["simulation_times_mean"], + color=greens[1], align="center", width=width, alpha=1, hatch=hatches[1], + edgecolor='w', bottom=heights_cuquantum["import_time"] + heights_cuquantum["creation_time"]) + + plt.xlabel("Number of qubits") + plt.ylabel("Execution time (sec)") + + legend_elements = [ + Patch(facecolor="w", edgecolor="k", hatch=hatches[0], label="Dry run time"), + Patch(facecolor="w", edgecolor="k", hatch=hatches[1], label="Simulation time"), + Patch(color=greys[1], label="Import time"), + Patch(color=oranges[1], label="numba"), + Patch(color=purples[1], label="cupy"), + Patch(color=greens[1], label="cuquantum"), + ] + plt.legend(handles=legend_elements) + if save: + plt.savefig(f"qibojit_dry_vs_simulation_{circuit}_{precision}.pdf", bbox_inches="tight") + else: + plt.show() + + +def plot_breakdown_circuits(data, nqubits, precision="double", width=0.1, fontsize=30, save=False): + """Creates dry run vs simulation barplot with import time breakdown for given number of qubits varying the circuit.""" + matplotlib.rcParams["font.size"] = fontsize + # Set plot params + hatches = ['/', '\\', 'o', '-', 'x', '.', '*'] + width = 0.1 + quantities = ["import_time", "creation_time", "dry_run_time", "simulation_times_mean"] + + circuits = ["qft", "variational", "supremacy", "qv", "bv"] + greys = sns.color_palette("Greys", 2) + purples = sns.color_palette("Purples", 2) + greens = sns.color_palette("Greens", 2) + + plt.figure(figsize=(25, 9)) + plt.title(f"qibojit - Dry run vs simulation - {nqubits} qubits") + + xvalues = np.array(range(len(circuits))) + plt.xticks(xvalues, circuits) + + base_condition = ((data["precision"] == precision) & (data["nqubits"] == nqubits)) + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=cupy") + heights_cupy = {q: np.array([float(data[condition & (data["circuit"] == circ)][q]) for circ in circuits]) + for q in quantities} + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=cuquantum") + heights_cuquantum = {q: np.array([float(data[condition & (data["circuit"] == circ)][q]) for circ in circuits]) + for q in quantities} + + plt.bar(xvalues - 3 * width / 2, heights_cupy["import_time"], + color=greys[0], align="center", width=width, alpha=1, hatch=hatches[0], + edgecolor='w') + plt.bar(xvalues - width / 2, heights_cupy["import_time"], + color=greys[1], align="center", width=width, alpha=1, hatch=hatches[1], + edgecolor='w') + + plt.bar(xvalues + width / 2, heights_cuquantum["import_time"], + color=greys[0], align="center", width=width, alpha=1, hatch=hatches[0], + edgecolor='w') + plt.bar(xvalues + 3 * width / 2, heights_cuquantum["import_time"], + color=greys[1], align="center", width=width, alpha=1, hatch=hatches[1], + edgecolor='w') + + plt.bar(xvalues - 3 * width / 2, heights_cupy["dry_run_time"], + color=purples[0], align="center", width=width, alpha=1, hatch=hatches[0], + edgecolor='w', bottom=heights_cupy["import_time"] + heights_cupy["creation_time"]) + plt.bar(xvalues - width / 2, heights_cupy["simulation_times_mean"], + color=purples[1], align="center", width=width, alpha=1, hatch=hatches[1], + edgecolor='w', bottom=heights_cupy["import_time"] + heights_cupy["creation_time"]) + + plt.bar(xvalues + width / 2, heights_cuquantum["dry_run_time"], + color=greens[0], align="center", width=width, alpha=1, hatch=hatches[0], + edgecolor='w', bottom=heights_cuquantum["import_time"] + heights_cuquantum["creation_time"]) + plt.bar(xvalues + 3 * width / 2, heights_cuquantum["simulation_times_mean"], + color=greens[1], align="center", width=width, alpha=1, hatch=hatches[1], + edgecolor='w', bottom=heights_cuquantum["import_time"] + heights_cuquantum["creation_time"]) + + plt.ylabel("Execution time (sec)") + + legend_elements = [ + Patch(facecolor="w", edgecolor="k", hatch=hatches[0], label="Dry run time"), + Patch(facecolor="w", edgecolor="k", hatch=hatches[1], label="Simulation time"), + Patch(color=greys[1], label="Import time"), + Patch(color=purples[1], label="cupy"), + Patch(color=greens[1], label="cuquantum"), + + ] + plt.legend(handles=legend_elements, bbox_to_anchor=(1,1)) + + if save: + plt.savefig(f"qibojit_dry_vs_simulation_{nqubits}qubits_{precision}.pdf", bbox_inches="tight") + else: + plt.show() + + +def plot_multigpu(data, nqubits, quantity, precision="double", fontsize=45, legend=False, save=False): + matplotlib.rcParams["font.size"] = fontsize + # Set plot params + hatches = ['/', '\\', 'o', '-', 'x', '.', '*'] + width = 0.1 + quantities = ["import_time", "creation_time", "dry_run_time", "simulation_times_mean", + "total_simulation_time", "total_dry_time"] + circuits = ["qft", "variational", "supremacy", "qv", "bv"] + + widths = [-5 * width / 2, - 3 * width / 2, -width / 2, width / 2, 3 * width / 2, 5 * width / 2] + oranges = sns.color_palette("Oranges", 3) + purples = sns.color_palette("Purples", 3) + greens = sns.color_palette("Greens", 3) + + # Plot the results + plt.figure(figsize=(25, 9)) + + xvalues = np.array(range(len(circuits))) + plt.xticks(xvalues, circuits) + + base_condition = ((data["precision"] == precision) & (data["nqubits"] == nqubits)) + heights1 = {} + heights2 = {} + heights4 = {} + for backend in ["qibojit", "qibotf"]: + condition = base_condition & (data["library_options"] == f"backend={backend},accelerators=4/GPU:3") + heights1[backend] = {q: np.array([float(data[condition & (data["circuit"] == c)][q]) for c in circuits]) + for q in quantities} + condition = base_condition & (data["library_options"] == f"backend={backend},accelerators=2/GPU:2+2/GPU:3") + heights2[backend] = {q: np.array([float(data[condition & (data["circuit"] == c)][q]) for c in circuits]) + for q in quantities} + condition = base_condition & (data["library_options"] == f"backend={backend},accelerators=1/GPU:0+1/GPU:1+1/GPU:2+1/GPU:3") + heights4[backend] = {q: np.array([float(data[condition & (data["circuit"] == c)][q]) for c in circuits]) + for q in quantities} + + plt.bar(xvalues + widths[0], heights1["qibojit"][quantity], + color=purples[0], align="center", width=width, alpha=1, hatch=hatches[0], edgecolor='w') + plt.bar(xvalues + widths[1], heights1["qibotf"][quantity], + color=greens[0], align="center", width=width, alpha=1, hatch=hatches[1], edgecolor='w') + + plt.bar(xvalues + widths[2], heights2["qibojit"][quantity], + color=purples[1], align="center", width=width, alpha=1, hatch=hatches[0], edgecolor='w') + plt.bar(xvalues + widths[3], heights2["qibotf"][quantity], + color=greens[1], align="center", width=width, alpha=1, hatch=hatches[1], edgecolor='w') + + plt.bar(xvalues + widths[4], heights4["qibojit"][quantity], + color=purples[2], align="center", width=width, alpha=1, hatch=hatches[0], edgecolor='w') + plt.bar(xvalues + widths[5], heights4["qibotf"][quantity], + color=greens[2], align="center", width=width, alpha=1, hatch=hatches[1], edgecolor='w') + + plt.title(f"Multi-GPU - {nqubits} qubits") + if quantity == "total_dry_time": + plt.ylabel("Total dry run time (sec)") + elif quantity == "dry_run_time": + plt.ylabel("Dry run time (sec)") + elif quantity == "total_simulation_time": + plt.ylabel("Total simulation time (sec)") + elif quantity == "simulation_times_mean": + plt.ylabel("Simulation time (sec)") + + legend_elements = [ + Patch(facecolor=purples[2], edgecolor="w", hatch=hatches[0], label="qibojit"), + Patch(facecolor=greens[2], edgecolor="w", hatch=hatches[1], label="qibotf"), + Patch(color=purples[0], label="1x GPU"), + Patch(color=purples[1], label="2x GPUs"), + Patch(color=purples[2], label="4x GPUs"), + ] + if legend: + plt.legend(handles=legend_elements, bbox_to_anchor=(1,1)) + if save: + plt.savefig(f"multigpu_{nqubits}qubits_{quantity}_{precision}.pdf", bbox_inches="tight") + else: + plt.show() diff --git a/qibojit-benchmarks/plots/devices.py b/qibojit-benchmarks/plots/devices.py new file mode 100644 index 0000000..596ba9e --- /dev/null +++ b/qibojit-benchmarks/plots/devices.py @@ -0,0 +1,44 @@ +"""Scaling plots with performance comparison of qibojit backend run on different CPU and GPU devices.""" +import matplotlib +import matplotlib.pyplot as plt +import seaborn as sns +matplotlib.rcParams['mathtext.fontset'] = 'cm' +matplotlib.rcParams['font.family'] = 'STIXGeneral' + + +class Line: + + def __init__(self, label, data, color, marker, linestyle="-"): + self.label = label + self.data = data + self.color = color + self.marker = marker + self.linestyle = linestyle + + +def plot_devices(lines, circuit, quantity, precision="double", + fontsize=30, legendfont=None, save=False): + matplotlib.rcParams["font.size"] = fontsize + # Filter data + for line in lines: + condition = (line.data["circuit"] == circuit) & (line.data["precision"] == precision) + line.data = line.data[condition] + + plt.figure(figsize=(14, 8)) + for line in lines: + plt.semilogy(line.data["nqubits"], line.data[quantity], color=line.color, linestyle=line.linestyle, + linewidth=3.0, marker=line.marker, markersize=10, label=line.label) + + plt.title(f"qibojit, {circuit}, {precision} precision") + plt.xlabel("Number of qubits") + if quantity == "total_dry_time": + plt.ylabel("Total dry run time (sec)") + elif quantity == "total_simulation_time": + plt.ylabel("Total simulation time (sec)") + + if legendfont is not None: + plt.legend(loc="upper left", fontsize=legendfont) + if save: + plt.savefig(f"devices_{circuit}_{quantity}_{precision}.pdf", bbox_inches="tight") + else: + plt.show() \ No newline at end of file diff --git a/qibojit-benchmarks/plots/evolution.py b/qibojit-benchmarks/plots/evolution.py new file mode 100644 index 0000000..94c8dee --- /dev/null +++ b/qibojit-benchmarks/plots/evolution.py @@ -0,0 +1,101 @@ +import numpy as np +import matplotlib +import matplotlib.pyplot as plt +import seaborn as sns +from matplotlib.patches import Patch +matplotlib.rcParams['mathtext.fontset'] = 'cm' +matplotlib.rcParams['font.family'] = 'STIXGeneral' + + +def plot_dense(data, quantity, nqubits, fontsize=30, legend=True, save=False): + matplotlib.rcParams["font.size"] = fontsize + + cpu_cp = sns.color_palette("Oranges", 4) + gpu_cp = sns.color_palette("Purples", 4) + + data["is_gpu"] = data["device"].apply(lambda x: "GPU" in x) + base_condition = (data["nqubits"] == nqubits) & (data["dense"] == True) + + plt.figure(figsize=(16, 9)) + condition = base_condition & (data["backend"] == "numpy") + plt.semilogy(data[condition]["dt"], data[condition][quantity], marker="^", markersize=10, + color=cpu_cp[3], linewidth=3.0, label="numpy") + condition = base_condition & (data["backend"] == "tensorflow") & (data["is_gpu"] == False) + plt.semilogy(data[condition]["dt"], data[condition][quantity], marker="o", markersize=10, + color=cpu_cp[1], linewidth=3.0, label="tensorflow cpu") + + condition = base_condition & (data["backend"] == "qibojit") & (data["platform"] == "cuquantum") + plt.semilogy(data[condition]["dt"], data[condition][quantity], marker="^", markersize=10, + color=gpu_cp[3], linewidth=3.0, label="cupy") + condition = base_condition & (data["backend"] == "tensorflow") & (data["is_gpu"] == True) + plt.semilogy(data[condition]["dt"], data[condition][quantity], marker="o", markersize=10, + color=gpu_cp[1], linewidth=3.0, label="tensorflow gpu") + + plt.title(f"Dense adiabatic evolution, {nqubits} qubits, double precision") + plt.xlabel("$\delta t$") + if quantity == "total_dry_time": + plt.ylabel("Total dry run time (sec)") + elif quantity == "total_simulation_time": + plt.ylabel("Total simulation time (sec)") + + if legend: + plt.legend() + + if save: + plt.savefig(f"evolution_dense_{nqubits}qubits_{quantity}.pdf", bbox_inches="tight") + else: + plt.show() + + +def plot_trotter(data, quantity, nqubits, fontsize=30, yticks=None, legend=False, save=False): + matplotlib.rcParams["font.size"] = fontsize + + cpu_cp = sns.color_palette("Oranges", 4) + gpu_cp = sns.color_palette("Purples", 4) + + data["is_gpu"] = data["device"].apply(lambda x: "GPU" in x) + base_condition = (data["nqubits"] == nqubits) & (data["dense"] == False) + + plt.figure(figsize=(16, 9)) + condition = base_condition & (data["backend"] == "numpy") + plt.semilogy(data[condition]["dt"], data[condition][quantity], marker="s", markersize=10, + color=cpu_cp[0], linewidth=3.0, label="numpy") + condition = base_condition & (data["backend"] == "tensorflow") & (data["is_gpu"] == False) + plt.semilogy(data[condition]["dt"], data[condition][quantity], marker="o", markersize=10, + color=cpu_cp[1], linewidth=3.0, label="tensorflow cpu") + condition = base_condition & (data["backend"] == "qibotf") & (data["is_gpu"] == False) + plt.semilogy(data[condition]["dt"], data[condition][quantity], marker="D", markersize=10, + color=cpu_cp[2], linewidth=3.0, label="qibotf cpu") + condition = base_condition & (data["backend"] == "qibojit") & (data["platform"] == "numba") + plt.semilogy(data[condition]["dt"], data[condition][quantity], marker="^", markersize=10, + color=cpu_cp[3], linewidth=3.0, label="qibojit (numba) cpu") + + condition = base_condition & (data["backend"] == "tensorflow") & (data["is_gpu"] == True) + plt.semilogy(data[condition]["dt"], data[condition][quantity], marker="o", markersize=10, + color=gpu_cp[1], linewidth=3.0, label="tensorflow gpu") + condition = base_condition & (data["backend"] == "qibotf") & (data["is_gpu"] == True) + plt.semilogy(data[condition]["dt"], data[condition][quantity], marker="D", markersize=10, + color=gpu_cp[2], linewidth=3.0, label="qibotf gpu") + condition = base_condition & (data["backend"] == "qibojit") & (data["platform"] == "cupy") + plt.semilogy(data[condition]["dt"], data[condition][quantity], marker="^", markersize=10, + color=gpu_cp[3], linewidth=3.0, label="qibojit (cupy) gpu") + condition = base_condition & (data["backend"] == "qibojit") & (data["platform"] == "cuquantum") + plt.semilogy(data[condition]["dt"], data[condition][quantity], marker="v", markersize=10, linestyle="--", + color=gpu_cp[3], linewidth=3.0, label="qibojit (cuquantum) gpu") + + plt.title(f"Trotter adiabatic evolution, {nqubits} qubits, double precision") + plt.xlabel("$\delta t$") + if quantity == "total_dry_time": + plt.ylabel("Total dry run time (sec)") + elif quantity == "total_simulation_time": + plt.ylabel("Total simulation time (sec)") + + if legend: + plt.legend(fontsize="small") + if yticks is not None: + plt.minorticks_off() + plt.yticks(yticks) + if save: + plt.savefig(f"evolution_trotter_{nqubits}qubits_{quantity}.pdf", bbox_inches="tight") + else: + plt.show() diff --git a/qibojit-benchmarks/plots/fusion.py b/qibojit-benchmarks/plots/fusion.py new file mode 100644 index 0000000..d4c495e --- /dev/null +++ b/qibojit-benchmarks/plots/fusion.py @@ -0,0 +1,145 @@ +"""Generates bar plots for gate fusion comparisons.""" +import numpy as np +import matplotlib +import matplotlib.pyplot as plt +import seaborn as sns +from matplotlib.patches import Patch +matplotlib.rcParams['mathtext.fontset'] = 'cm' +matplotlib.rcParams['font.family'] = 'STIXGeneral' + + +def plot_fusion_nqubits(data, circuit, quantity, precision="double", width=0.1, fontsize=30, legend=False, logscale=False, save=False): + matplotlib.rcParams["font.size"] = fontsize + + # Set plot params + hatches = ['/', '\\', 'o', '-', 'x', '.', '*'] + + nqubits = [20, 22, 24, 26, 28] + widths = [-5 * width / 2, - 3 * width / 2, -width / 2, width / 2, 3 * width / 2, 5 * width / 2] + oranges = sns.color_palette("Oranges", 2) + purples = sns.color_palette("Purples", 2) + greens = sns.color_palette("Greens", 2) + + # Plot the results + plt.figure(figsize=(25, 9)) + plt.title(f"qibojit - Gate fusion - {circuit}") + + xvalues = np.array(range(len(nqubits))) + plt.xticks(xvalues, nqubits) + + base_condition = ((data["precision"] == precision) & (data["circuit"] == circuit)) + + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=numba") + heights = np.array([float(data[condition & (data["nqubits"] == n)][quantity]) for n in nqubits]) + plt.bar(xvalues + widths[0], heights, color=oranges[0], align="center", width=width, + log=logscale, alpha=1, hatch=hatches[0], edgecolor='w') + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=numba,max_qubits=2") + heights = np.array([float(data[condition & (data["nqubits"] == n)][quantity]) for n in nqubits]) + plt.bar(xvalues + widths[1], heights, color=oranges[1], align="center", width=width, + log=logscale, alpha=1, hatch=hatches[1], edgecolor='w') + + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=cupy") + heights = np.array([float(data[condition & (data["nqubits"] == n)][quantity]) for n in nqubits]) + plt.bar(xvalues + widths[2], heights, color=purples[0], align="center", width=width, + log=logscale, alpha=1, hatch=hatches[0], edgecolor='w') + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=cupy,max_qubits=2") + heights = np.array([float(data[condition & (data["nqubits"] == n)][quantity]) for n in nqubits]) + plt.bar(xvalues + widths[3], heights, color=purples[1], align="center", width=width, + log=logscale, alpha=1, hatch=hatches[1], edgecolor='w') + + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=cuquantum") + heights = np.array([float(data[condition & (data["nqubits"] == n)][quantity]) for n in nqubits]) + plt.bar(xvalues + widths[4], heights, color=greens[0], align="center", width=width, + log=logscale, alpha=1, hatch=hatches[0], edgecolor='w') + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=cuquantum,max_qubits=2") + heights = np.array([float(data[condition & (data["nqubits"] == n)][quantity]) for n in nqubits]) + plt.bar(xvalues + widths[5], heights, color=greens[1], align="center", width=width, + log=logscale, alpha=1, hatch=hatches[1], edgecolor='w') + + plt.xlabel("Number of qubits") + if quantity == "total_dry_time": + plt.ylabel("Total dry run time (sec)") + elif quantity == "total_simulation_time": + plt.ylabel("Total simulation time (sec)") + + if legend: + legend_elements = [ + Patch(facecolor="w", edgecolor="k", hatch=hatches[0], label="No fusion"), + Patch(facecolor="w", edgecolor="k", hatch=hatches[1], label="Two-qubit fusion"), + Patch(color=oranges[1], label="numba"), + Patch(color=purples[1], label="cupy"), + Patch(color=greens[1], label="cuquantum") + ] + plt.legend(handles=legend_elements) + if save: + plt.savefig(f"qibojit_fusion_{precision}_{circuit}_{quantity}.pdf", bbox_inches="tight") + else: + plt.show() + + +def plot_fusion_circuits(data, nqubits, quantity, precision="double", width=0.1, fontsize=30, legend=False, logscale=False, save=False): + matplotlib.rcParams["font.size"] = fontsize + + # Set plot params + hatches = ['/', '\\', 'o', '-', 'x', '.', '*'] + + circuits = ["qft", "variational", "supremacy", "qv", "bv"] + widths = [-5 * width / 2, - 3 * width / 2, -width / 2, width / 2, 3 * width / 2, 5 * width / 2] + oranges = sns.color_palette("Oranges", 2) + purples = sns.color_palette("Purples", 2) + greens = sns.color_palette("Greens", 2) + + # Plot the results + plt.figure(figsize=(25, 9)) + plt.title(f"qibojit - Gate fusion - {nqubits} qubits") + + xvalues = np.array(range(len(circuits))) + plt.xticks(xvalues, circuits) + + base_condition = ((data["precision"] == precision) & (data["nqubits"] == nqubits)) + + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=numba") + heights = np.array([float(data[condition & (data["circuit"] == c)][quantity]) for c in circuits]) + plt.bar(xvalues + widths[0], heights, color=oranges[0], align="center", width=width, + log=logscale, alpha=1, hatch=hatches[0], edgecolor='w') + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=numba,max_qubits=2") + heights = np.array([float(data[condition & (data["circuit"] == c)][quantity]) for c in circuits]) + plt.bar(xvalues + widths[1], heights, color=oranges[1], align="center", width=width, + log=logscale, alpha=1, hatch=hatches[1], edgecolor='w') + + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=cupy") + heights = np.array([float(data[condition & (data["circuit"] == c)][quantity]) for c in circuits]) + plt.bar(xvalues + widths[2], heights, color=purples[0], align="center", width=width, + log=logscale, alpha=1, hatch=hatches[0], edgecolor='w') + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=cupy,max_qubits=2") + heights = np.array([float(data[condition & (data["circuit"] == c)][quantity]) for c in circuits]) + plt.bar(xvalues + widths[3], heights, color=purples[1], align="center", width=width, + log=logscale, alpha=1, hatch=hatches[1], edgecolor='w') + + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=cuquantum") + heights = np.array([float(data[condition & (data["circuit"] == c)][quantity]) for c in circuits]) + plt.bar(xvalues + widths[4], heights, color=greens[0], align="center", width=width, + log=logscale, alpha=1, hatch=hatches[0], edgecolor='w') + condition = base_condition & (data["library_options"] == "backend=qibojit,platform=cuquantum,max_qubits=2") + heights = np.array([float(data[condition & (data["circuit"] == c)][quantity]) for c in circuits]) + plt.bar(xvalues + widths[5], heights, color=greens[1], align="center", width=width, + log=logscale, alpha=1, hatch=hatches[1], edgecolor='w') + + if quantity == "total_dry_time": + plt.ylabel("Total dry run time (sec)") + elif quantity == "total_simulation_time": + plt.ylabel("Total simulation time (sec)") + + if legend: + legend_elements = [ + Patch(facecolor="w", edgecolor="k", hatch=hatches[0], label="No fusion"), + Patch(facecolor="w", edgecolor="k", hatch=hatches[1], label="Two-qubit fusion"), + Patch(color=oranges[1], label="numba"), + Patch(color=purples[1], label="cupy"), + Patch(color=greens[1], label="cuquantum") + ] + plt.legend(handles=legend_elements, bbox_to_anchor=(1,1)) + if save: + plt.savefig(f"qibojit_fusion_{precision}_{nqubits}qubits_{quantity}.pdf", bbox_inches="tight") + else: + plt.show() \ No newline at end of file diff --git a/qibojit-benchmarks/plots/libraries.py b/qibojit-benchmarks/plots/libraries.py new file mode 100644 index 0000000..f144582 --- /dev/null +++ b/qibojit-benchmarks/plots/libraries.py @@ -0,0 +1,73 @@ +import numpy as np +import pandas as pd +import matplotlib +import matplotlib.pyplot as plt +import seaborn as sns +from matplotlib.patches import Patch +matplotlib.rcParams['mathtext.fontset'] = 'cm' +matplotlib.rcParams['font.family'] = 'STIXGeneral' + + +class Library: + """Defines color, hatch and label for each library.""" + + def __init__(self, name, color, hatch, label, has_double=True, has_single=True, alpha=None): + self.name = name + self.color = color + self.hatch = hatch + self.label = label + self.has_double = has_double + self.has_single = has_single + if alpha is None: + self.alpha = 1.0 if "GPU" in self.name else 0.5 + else: + self.alpha = alpha + + def has(self, precision): + return getattr(self, f"has_{precision}") + + +def plot_libraries(libraries, cpu_data, gpu_data, quantity, nqubits, + precision="double", width=0.07, fontsize=45, + legend=False, logscale=True, fusion=False, save=False): + matplotlib.rcParams["font.size"] = fontsize + # Process data + gpu_data = gpu_data.copy() + gpu_data["library"] += " GPU" + data = pd.concat([cpu_data, gpu_data]) + + circuits = ["qft", "variational", "supremacy", "qv", "bv"] + # create widths list for bar positioning + n = len([library for library in libraries if library.has(precision)]) + ws = np.arange(n) + ws = iter((ws - ws[n // 2]) * width) + + # Plot the results + xvalues = np.array(range(len(circuits))) + plt.figure(figsize=(25, 9)) + base_condition = (data["nqubits"] == nqubits) & (data["precision"] == precision) + for library in libraries: + if library.has(precision): + condition = base_condition & (data["library"] == library.name) + height = np.array([float(data[condition & (data["circuit"] == c)][quantity]) for c in circuits]) + plt.bar(xvalues + next(ws), height, color=library.color, align="center", + width=width, alpha=library.alpha, label=library.label, + log=logscale, edgecolor='w', hatch=library.hatch) + + if fusion: + plt.title(f"{nqubits} qubits - Two-qubit fusion - {precision} precision") + else: + plt.title(f"{nqubits} qubits - {precision} precision") + if quantity == "total_dry_time": + plt.ylabel("Total dry time (sec)") + elif quantity == "total_simulation_time": + plt.ylabel("Total simulation time (sec)") + + plt.xticks(xvalues, circuits) + if legend: + plt.legend(fontsize="small", bbox_to_anchor=(1,1)) + if save: + savename = "libraries_fusion" if fusion else "libraries" + plt.savefig(f"{savename}_{precision}_{nqubits}qubits_{quantity}.pdf", bbox_inches="tight") + else: + plt.show() diff --git a/qibojit-benchmarks/plots/paper-plots.ipynb b/qibojit-benchmarks/plots/paper-plots.ipynb new file mode 100644 index 0000000..04b78c7 --- /dev/null +++ b/qibojit-benchmarks/plots/paper-plots.ipynb @@ -0,0 +1,337 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "76badf4a", + "metadata": {}, + "source": [ + "This notebook shows how to generate the benchmark plots used in the paper. \n", + "\n", + "To generate the plots using the benchmarks of the original paper, download the following gist:\n", + "\n", + "[stavros11/ffb88a5b914b60213515f0256c0e8aa4](https://gist.github.com/stavros11/ffb88a5b914b60213515f0256c0e8aa4)\n", + "\n", + "copy the contents in a folder named `/data` in the directory of this notebook and execute all cells.\n", + "\n", + "The logs provided in the gist serve as a template.\n", + "The same plotting functionality should work with logs generated from different machines.\n", + "To generate new logs from scratch one can use the bash scripts provided in the `scripts/` directory." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "97f43ed0", + "metadata": {}, + "outputs": [], + "source": [ + "from utils import load_data, load_evolution_data\n", + "\n", + "save = False # if ``True`` plots will be saved in the current directory as pdfs" + ] + }, + { + "cell_type": "markdown", + "id": "f4d6d97f", + "metadata": {}, + "source": [ + "## Figure 3\n", + "\n", + "Bar plot with import breakdown and dry run vs simulation comparison for qibojit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba717379", + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "from barplots import plot_breakdown_nqubits\n", + "\n", + "data = load_data(f\"./data/qibojit_breakdown.dat\")\n", + "plot_breakdown_nqubits(data, \"supremacy\", save=save)" + ] + }, + { + "cell_type": "markdown", + "id": "6e2eeb0b", + "metadata": {}, + "source": [ + "## Figure 4\n", + "\n", + "Scaling plots of execution time as a function of the number of qubits for all qibo backends." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ba0a87d", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "from scaling import plot_scaling\n", + "\n", + "cpu_data = load_data(\"./data/qibo_scaling_cpu.dat\")\n", + "gpu_data = load_data(\"./data/qibo_scaling_gpu.dat\")\n", + "\n", + "plot_scaling(cpu_data, gpu_data, \"qft\", \"total_dry_time\", legend=False, save=save)\n", + "plot_scaling(cpu_data, gpu_data, \"qft\", \"total_simulation_time\", save=save)" + ] + }, + { + "cell_type": "markdown", + "id": "8cb5555a", + "metadata": {}, + "source": [ + "## Figure 5\n", + "\n", + "qibojit backend performance on different CPU and GPU devices." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "895cb582", + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "import seaborn as sns # used for color palettes\n", + "from devices import Line, plot_devices\n", + "\n", + "oranges = sns.color_palette(\"Oranges\", 2)\n", + "greens = sns.color_palette(\"Greens\", 2)\n", + "blues = sns.color_palette(\"Blues\", 3)\n", + "\n", + "lines = [\n", + " Line(\"NVIDIA RTX A6000 (cupy)\", load_data(\"./data/qibo_scaling_gpu.dat\", qibojit_only=True), blues[2], \"o\"),\n", + " Line(\"NVIDIA DGX V100 (cupy)\", load_data(\"./data/dgx_qibojit.dat\", qibojit_only=True), blues[1], \"^\"),\n", + " Line(\"NVIDIA GTX 1650 (cupy)\", load_data(\"./data/gtx1650_qibojit.dat\", qibojit_only=True), blues[0], \"d\"),\n", + " Line(\"AMD Radeon VII (cupy)\", load_data(\"./data/rocm_qibojit.dat\"), greens[1], \"v\"),\n", + " Line(\"NVIDIA RTX A6000 (cupy-multigpu)\", load_data(\"./data/rtx_multigpu.dat\", qibojit_only=True), blues[2], \"o\", linestyle=\"--\"),\n", + " Line(\"AMD EPYC 7742, 128 th., 2TB (numba)\", load_data(\"./data/qibo_scaling_cpu.dat\", qibojit_only=True), oranges[1], \"\"),\n", + " Line(\"ATOS QLM, 384 th., 6TB (numba)\", load_data(\"./data/qlm_qibojit.dat\", qibojit_only=True), oranges[0], \"\"),\n", + "]\n", + "# filter qibojit-cupy only data if the log file contains more (eg. qibojit-cuquantum)\n", + "is_cupy = lines[0].data[\"library_options\"].apply(lambda x: \"cupy\" in x)\n", + "lines[0].data = lines[0].data[is_cupy == True]\n", + "\n", + "plot_devices(lines, \"qft\", \"total_dry_time\", save=save)\n", + "plot_devices(lines, \"qft\", \"total_simulation_time\", legendfont=26, save=save)" + ] + }, + { + "cell_type": "markdown", + "id": "5cd4c946", + "metadata": {}, + "source": [ + "## Figure 6\n", + "\n", + "Bar plot with different multigpu configurations and qibojit vs qibotf comparison." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "15099a54", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "from barplots import plot_multigpu\n", + "\n", + "data = load_data(\"./data/dgx_multigpu.dat\")\n", + "plot_multigpu(data, 32, \"total_dry_time\", save=save)\n", + "plot_multigpu(data, 32, \"total_simulation_time\", legend=True, save=save)" + ] + }, + { + "cell_type": "markdown", + "id": "64538a61", + "metadata": {}, + "source": [ + "## Figure 7\n", + "\n", + "Bar plot with comparisons between different simulation libraries on various circuits." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "81035c0e", + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "from libraries import Library, plot_libraries\n", + "\n", + "cpu_data = load_data(\"./data/libraries_cpu.dat\")\n", + "gpu_data = load_data(\"./data/libraries_gpu.dat\")\n", + "\n", + "palette = sns.color_palette(\"bright\", 7)\n", + "libraries = [\n", + " Library(\"qibo\", palette[0], \"/\", \"Qibo\"),\n", + " Library(\"qibo GPU\", palette[0], \"/\", \"Qibo GPU\"),\n", + " Library(\"qiskit\", palette[1], \"-\", \"Qiskit\"),\n", + " Library(\"qiskit-gpu GPU\", palette[1], \"-\", \"Qiskit GPU\"),\n", + " Library(\"hybridq\", palette[2], \"x\", \"HybridQ\"),\n", + " Library(\"hybridq-gpu GPU\", palette[2], \"x\", \"HybridQ GPU\"),\n", + " Library(\"qulacs\", palette[4], \"\\\\\", \"Qulacs\", has_single=False),\n", + " Library(\"qulacs-gpu GPU\", palette[4], \"\\\\\", \"Qulacs GPU\", has_single=False),\n", + " Library(\"projectq\", palette[3], \"o\", \"ProjectQ\", has_single=False),\n", + " Library(\"qcgpu GPU\", palette[3], \"o\", \"QCGPU\", has_double=False)\n", + "]\n", + "\n", + "plot_libraries(libraries, cpu_data, gpu_data, \"total_dry_time\", 20, precision=\"single\", legend=False, save=save)\n", + "plot_libraries(libraries, cpu_data, gpu_data, \"total_dry_time\", 30, precision=\"single\", legend=True, save=save)\n", + "\n", + "plot_libraries(libraries, cpu_data, gpu_data, \"total_dry_time\", 20, precision=\"double\", legend=False, save=save)\n", + "plot_libraries(libraries, cpu_data, gpu_data, \"total_dry_time\", 30, precision=\"double\", legend=True, save=save)" + ] + }, + { + "cell_type": "markdown", + "id": "20eb1827", + "metadata": {}, + "source": [ + "## Figure 8\n", + "\n", + "Bar plot comparing fusion vs no fusion for all qibojit platforms and circuits" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e0a127b8", + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "from fusion import plot_fusion_circuits\n", + "\n", + "data = load_data(f\"./data/qibojit_fusion.dat\")\n", + "plot_fusion_circuits(data, 30, \"total_dry_time\", fontsize=38, legend=True, save=True)\n", + "plot_fusion_circuits(data, 30, \"total_simulation_time\", fontsize=38, legend=True, save=save)" + ] + }, + { + "cell_type": "markdown", + "id": "909fbaeb", + "metadata": {}, + "source": [ + "## Figure 9\n", + "\n", + "Bar plot comparing two-qubit fusion for different libraries (qibo, qiskit, qsim)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60e32824", + "metadata": {}, + "outputs": [], + "source": [ + "import seaborn as sns\n", + "from libraries import Library, plot_libraries\n", + "\n", + "cpu_data = load_data(\"./data/libraries_fusion_cpu.dat\")\n", + "gpu_data = load_data(\"./data/libraries_fusion_gpu.dat\")\n", + "\n", + "palette = sns.color_palette(\"bright\", 7)\n", + "libraries = [\n", + " Library(\"qibo\", palette[0], \"/\", \"Qibo (numba)\", alpha=0.3),\n", + " Library(\"qibo GPU\", palette[0], \"/\", \"Qibo (cupy) GPU\", alpha=1.0),\n", + " Library(\"qibo-cuquantum GPU\", palette[0], \"/\", \"Qibo (cuquantum) GPU\", alpha=0.6),\n", + " Library(\"qiskit\", palette[1], \"-\", \"Qiskit\", alpha=0.3),\n", + " Library(\"qiskit-gpu GPU\", palette[1], \"-\", \"Qiskit GPU\", alpha=1.0),\n", + " Library(\"qsim\", palette[2], \"\\\\\", \"qsim\", has_double=False, alpha=0.3),\n", + " Library(\"qsim-gpu GPU\", palette[2], \"\\\\\", \"qsim GPU\", has_double=False, alpha=1.0),\n", + " Library(\"qsim-cuquantum GPU\", palette[2], \"\\\\\", \"qsim (cuquantum) GPU\", has_double=False, alpha=0.6),\n", + "]\n", + "\n", + "plot_libraries(libraries, cpu_data, gpu_data, \"total_dry_time\", 30, precision=\"single\", \n", + " legend=True, fontsize=45, logscale=True, fusion=True, save=save)" + ] + }, + { + "cell_type": "markdown", + "id": "a5392b2d", + "metadata": {}, + "source": [ + "## Figure 10\n", + "\n", + "Scaling plot vs time dt for adiabatic evolution of TFIM Hamiltonian using the dense form." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6d7c77f0", + "metadata": {}, + "outputs": [], + "source": [ + "from evolution import plot_dense\n", + "\n", + "data = load_evolution_data(\"data/evolution.dat\")\n", + "plot_dense(data, \"total_dry_time\", 10, save=save)" + ] + }, + { + "cell_type": "markdown", + "id": "0b8e88e7", + "metadata": {}, + "source": [ + "## Figure 11\n", + "\n", + "Scaling plot vs time dt for adiabatic evolution of TFIM Hamiltonian using the Trotter decomposition." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67f4884c", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "from evolution import plot_trotter\n", + "\n", + "data = load_evolution_data(\"data/evolution.dat\")\n", + "plot_trotter(data, \"total_dry_time\", 10, yticks=[1, 10], legend=True, save=save)\n", + "plot_trotter(data, \"total_dry_time\", 20, yticks=[1, 10, 100], legend=False, save=save)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qibojit-benchmarks/plots/scaling.py b/qibojit-benchmarks/plots/scaling.py new file mode 100644 index 0000000..875eac5 --- /dev/null +++ b/qibojit-benchmarks/plots/scaling.py @@ -0,0 +1,56 @@ +"""Generates qubit scaling plots for different qibo backends.""" +import matplotlib +import matplotlib.pyplot as plt +import seaborn as sns +from matplotlib.patches import Patch +matplotlib.rcParams['mathtext.fontset'] = 'cm' +matplotlib.rcParams['font.family'] = 'STIXGeneral' + + +def plot_scaling(cpu_data, gpu_data, circuit, quantity, precision="double", fontsize=30, legend=True, save=False): + matplotlib.rcParams["font.size"] = fontsize + # Prepare GPU data + condition = (gpu_data["circuit"] == circuit) & (gpu_data["precision"] == precision) + backends = ["tensorflow", "qibotf"] + data = {f"{k} GPU": gpu_data[(gpu_data["library_options"] == f"backend={k}") & condition] for k in backends} + data["qibojit cupy GPU"] = gpu_data[(gpu_data["library_options"] == "backend=qibojit,platform=cupy") & condition] + data["qibojit cuquantum GPU"] = gpu_data[(gpu_data["library_options"] == "backend=qibojit,platform=cuquantum") & condition] + + # Prepare CPU data + backends = ["numpy", "tensorflow", "qibotf", "qibojit"] + condition = (cpu_data["circuit"] == circuit) & (cpu_data["precision"] == precision) + data.update({k: cpu_data[(cpu_data["library_options"] == f"backend={k}") & condition] for k in backends}) + + # Plot data + cpu_cp = sns.color_palette("Oranges", 4) + gpu_cp = sns.color_palette("Purples", 4) + plt.figure(figsize=(16, 9)) + plt.semilogy(data["numpy"]["nqubits"], data["numpy"][quantity], marker="s", markersize=10, + color=cpu_cp[0], linewidth=3.0, label="numpy") + plt.semilogy(data["tensorflow"]["nqubits"], data["tensorflow"][quantity], marker="o", markersize=10, + color=cpu_cp[1], linewidth=3.0, label="tensorflow cpu") + plt.semilogy(data["qibotf"]["nqubits"], data["qibotf"][quantity], marker="D", markersize=10, + color=cpu_cp[2], linewidth=3.0, label="qibotf cpu") + plt.semilogy(data["qibojit"]["nqubits"], data["qibojit"][quantity], + color=cpu_cp[3], linewidth=3.0, label="qibojit (numba) cpu", marker="^", markersize=10) + plt.semilogy(data["tensorflow GPU"]["nqubits"], data["tensorflow GPU"][quantity], marker="o", markersize=10, + color=gpu_cp[1], linewidth=3.0, label="tensorflow gpu") + plt.semilogy(data["qibotf GPU"]["nqubits"], data["qibotf GPU"][quantity], marker="D", markersize=10, + color=gpu_cp[2], linewidth=3.0, label="qibotf gpu") + plt.semilogy(data["qibojit cupy GPU"]["nqubits"], data["qibojit cupy GPU"][quantity], + color=gpu_cp[3], linewidth=3.0, label="qibojit (cupy) gpu", marker="^", markersize=10) + plt.semilogy(data["qibojit cuquantum GPU"]["nqubits"], data["qibojit cuquantum GPU"][quantity], + color=gpu_cp[3], linewidth=3.0, linestyle="--", label="qibojit (cuquantum) gpu", marker="v", markersize=10) + + plt.title(f"{circuit}, {precision} precision") + plt.xlabel("Number of qubits") + if quantity == "total_dry_time": + plt.ylabel("Total dry run time (sec)") + elif quantity == "total_simulation_time": + plt.ylabel("Total simulation time (sec)") + if legend: + plt.legend(fontsize="small") + if save: + plt.savefig(f"qibo_scaling_{circuit}_{quantity}_{precision}.pdf", bbox_inches="tight") + else: + plt.show() diff --git a/qibojit-benchmarks/plots/utils.py b/qibojit-benchmarks/plots/utils.py new file mode 100644 index 0000000..a2fbb7e --- /dev/null +++ b/qibojit-benchmarks/plots/utils.py @@ -0,0 +1,36 @@ +import json +import pandas as pd + + +def load_data(filename, qibojit_only=False): + with open(filename, "r") as file: + data = pd.DataFrame(json.load(file)) + + # filter data for qibojit + if qibojit_only: + is_qibojit = data["library_options"].apply(lambda x: "qibojit" in x) + data = data[is_qibojit] + + data["total_dry_time"] = data["dry_run_time"] + data["creation_time"] + data["import_time"] + data["total_simulation_time"] = data["simulation_times_mean"] + data["creation_time"] + data["import_time"] + return data + + +def load_data_multigpu(filename, qibojit_only=False): + data = load_data(filename, qibojit_only) + data["backend"] = data["library_options"].apply(lambda x: x.split(",")[0].split("=")[-1]) + data["accelerators"] = data["library_options"].apply(lambda x: x.split(",")[1].split("=")[-1]) + data["nqubits (accelerators)"] = data.apply(lambda x: f"{x.nqubits} ({x.accelerators})", axis=1) + return data + + +def load_evolution_data(filename): + with open(filename, "r") as file: + data = pd.DataFrame(json.load(file)) + + data["creation_time"] = data["hamiltonian_creation_time"] + data["evolution_creation_time"] + data["total_dry_time"] = data["dry_run_time"] + data["creation_time"] + data["import_time"] + #if "simulation_times_mean" not in data.columns: + # data["simulation_times_mean"] = data["simulation_times"].apply(lambda x: np.mean(x)) + data["total_simulation_time"] = data["simulation_times_mean"] + data["creation_time"] + data["import_time"] + return data \ No newline at end of file diff --git a/qibojit-benchmarks/scripts/README.md b/qibojit-benchmarks/scripts/README.md new file mode 100644 index 0000000..b4dd2cc --- /dev/null +++ b/qibojit-benchmarks/scripts/README.md @@ -0,0 +1,53 @@ +# Example scripts + +This folder contains example bash scripts that execute the `compare.py` benchmark for different circuit configurations and different libraries. +The available circuits are described in the main ``README.md`` of this repository +(some of them are presented in Table 1 of the [HyQuas paper](https://dl.acm.org/doi/pdf/10.1145/3447818.3460357)). + +The provided scripts are the following: + +### `qibo.sh` + +Execute a specific circuit with different sizes and different Qibo backends. +By default, it executes the circuit with all backends (NumPy, TensorFlow, Qibotf and Qibojit) and with circuit sizes from 3 to 31 (28 for NumPy). +If different platforms are available, it will use the default one. +It is straightforward to edit the bash script to fine tune the variation ranges and the number of repetitions for each circuit size. +A starter code to plot the results is available in ``plots/qibo.ipynb``. +Options: + - ``circuit``: circuit to execute (default: ``qft``) + - ``precision``: ``single`` or ``double`` (default: ``double``) + +### ``qibojit.sh`` + +Similar to ``qibo.sh``, but uses only qibojit. Useful to compare the performance across different platforms and different GPUs. +A starter code to plot the results is available in ``plots/qibojit.ipynb``. +Options: + - ``filename``: where to store the logs (default: ``qibojit.dat``) + - ``circuit``: circuit to execute (default: ``qft``) + - ``precision``: ``single`` or ``double`` (default: ``double``) + - ``nreps``: number of repetitions for each circuit size (default: ``10``) + - ``platform``: platform to use for simulation (default: ``cupy``) + +### ``qibo_circuits.sh`` + +Execute a set of circuits for different circuit sizes, using a specific backend. +Useful to plot the breakdown of execution times (starter code in ``plots/qibo.ipynb``). +Options: + - ``backend``: backend to use for simulation (default: ``qibojit``) + - ``precision``: ``single`` or ``double`` (default: ``double``) + - ``nreps``: number of repetitions for each circuit and size (default: ``10``) + +### ``library_single.sh`` + +Execute a set of circuits for different circuit sizes, using a variety of different libraries. +This script focuses on libraries that support single precision simulation. +It is straightforward to edit the bash script to change the circuit sizes and the libraries. +A starter code to plot the results is available in ``plots/libraries.ipynb``. +Options: + - ``nreps``: number of repetitions for each circuit size (default: ``10``) + +### ``library_double.sh`` + +Same as ``library_single.sh``, but with libraries that support double precision simulation. +Options: + - ``nreps``: number of repetitions for each circuit size (default: ``10``) diff --git a/qibojit-benchmarks/scripts/cu_tensornet_expectation.json b/qibojit-benchmarks/scripts/cu_tensornet_expectation.json new file mode 100644 index 0000000..1c124dc --- /dev/null +++ b/qibojit-benchmarks/scripts/cu_tensornet_expectation.json @@ -0,0 +1 @@ +{"MPI_enabled": false, "MPS_enabled": false, "NCCL_enabled": false, "expectation_enabled": true} diff --git a/qibojit-benchmarks/scripts/cu_tensornet_mpi_expectation.json b/qibojit-benchmarks/scripts/cu_tensornet_mpi_expectation.json new file mode 100644 index 0000000..079958f --- /dev/null +++ b/qibojit-benchmarks/scripts/cu_tensornet_mpi_expectation.json @@ -0,0 +1 @@ +{"MPI_enabled": true, "MPS_enabled": false, "NCCL_enabled": false, "expectation_enabled": true} diff --git a/qibojit-benchmarks/scripts/cu_tensornet_nccl_expectation.json b/qibojit-benchmarks/scripts/cu_tensornet_nccl_expectation.json new file mode 100644 index 0000000..3dd9e5c --- /dev/null +++ b/qibojit-benchmarks/scripts/cu_tensornet_nccl_expectation.json @@ -0,0 +1 @@ +{"MPI_enabled": false, "MPS_enabled": false, "NCCL_enabled": true, "expectation_enabled": true} diff --git a/qibojit-benchmarks/scripts/evolution_dense.sh b/qibojit-benchmarks/scripts/evolution_dense.sh new file mode 100755 index 0000000..05e3c7e --- /dev/null +++ b/qibojit-benchmarks/scripts/evolution_dense.sh @@ -0,0 +1,33 @@ +#! /usr/bin/bash +# Generates data for the evolution scaling with dt using the dense form of the Hamiltonian + +# Command-line parameters +: "${nqubits:=10}" +: "${precision:=double}" +: "${filename:=evolution.dat}" +: "${nreps:=5}" + + +# GPU backends +for dt in 0.1 0.095 0.09 0.085 0.08 0.075 0.07 0.065 0.06 0.055 0.05 0.045 \ + 0.04 0.035 0.03 0.025 0.02 0.015 0.01 0.005 +do + for backend in qibojit tensorflow + do + CUDA_VISIBLE_DEVICES=0 python evolution.py --nqubits $nqubits --dt $dt --filename $filename --nreps $nreps \ + --backend $backend --precision $precision --dense + echo + done +done + +# CPU backends +for dt in 0.1 0.095 0.09 0.085 0.08 0.075 0.07 0.065 0.06 0.055 0.05 0.045 \ + 0.04 0.035 0.03 0.025 0.02 0.015 0.01 0.005 +do + for backend in numpy tensorflow + do + CUDA_VISIBLE_DEVICES="" python evolution.py --nqubits $nqubits --dt $dt --filename $filename --nreps $nreps \ + --backend $backend --precision $precision --dense + echo + done +done diff --git a/qibojit-benchmarks/scripts/evolution_trotter.sh b/qibojit-benchmarks/scripts/evolution_trotter.sh new file mode 100755 index 0000000..a5a3519 --- /dev/null +++ b/qibojit-benchmarks/scripts/evolution_trotter.sh @@ -0,0 +1,45 @@ +#! /usr/bin/bash +# Generates data for the evolution scaling with dt using the Trotter decomposition + +# Command-line parameters +: "${nqubits:=10}" +: "${precision:=double}" +: "${filename:=evolution.dat}" +: "${nreps:=10}" + + +# GPU qibojit backend +for dt in 0.1 0.095 0.09 0.085 0.08 0.075 0.07 0.065 0.06 0.055 0.05 0.045 \ + 0.04 0.035 0.03 0.025 0.02 0.015 0.01 0.005 +do + for platform in cupy cuquantum + do + CUDA_VISIBLE_DEVICES="0" python evolution.py --nqubits $nqubits --dt $dt --filename $filename --nreps $nreps \ + --backend qibojit --platform $platform --precision $precision + echo + done +done + +# GPU tensorflow and qibotf backends +for dt in 0.1 0.095 0.09 0.085 0.08 0.075 0.07 0.065 0.06 0.055 0.05 0.045 \ + 0.04 0.035 0.03 0.025 0.02 0.015 0.01 0.005 +do + for backend in qibotf tensorflow + do + CUDA_VISIBLE_DEVICES="0" python evolution.py --nqubits $nqubits --dt $dt --filename $filename --nreps $nreps \ + --backend $backend --precision $precision + echo + done +done + +# CPU all backends +for dt in 0.1 0.095 0.09 0.085 0.08 0.075 0.07 0.065 0.06 0.055 0.05 0.045 \ + 0.04 0.035 0.03 0.025 0.02 0.015 0.01 0.005 +do + for backend in qibojit qibotf tensorflow numpy + do + CUDA_VISIBLE_DEVICES="" python evolution.py --nqubits $nqubits --dt $dt --filename $filename --nreps $nreps \ + --backend $backend --precision $precision + echo + done +done diff --git a/qibojit-benchmarks/scripts/fusion.sh b/qibojit-benchmarks/scripts/fusion.sh new file mode 100755 index 0000000..c0884a7 --- /dev/null +++ b/qibojit-benchmarks/scripts/fusion.sh @@ -0,0 +1,34 @@ +#! /usr/bin/bash +# Generate logs for fusion comparison on different circuits for all qibojit platforms + +# Command-line parameters +: "${filename:=qibojit_fusion.dat}" +: "${precision:=double}" +: "${nqubits:=30}" +: "${nreps_cpu:=3}" +: "${nreps_gpu:=5}" + + +for circuit in qft variational supremacy bv qv +do + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename \ + --library-options backend=qibojit,platform=cupy,max_qubits=2 --nreps $nreps_gpu --precision $precision + echo + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename \ + --library-options backend=qibojit,platform=cupy --nreps $nreps_gpu --precision $precision + echo + + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename \ + --library-options backend=qibojit,platform=cuquantum,max_qubits=2 --nreps $nreps_gpu --precision $precision + echo + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename \ + --library-options backend=qibojit,platform=cuquantum --nreps $nreps_gpu --precision $precision + echo + + CUDA_VISIBLE_DEVICES="" python compare.py --circuit $circuit --nqubits $nqubits --filename $filename \ + --library-options backend=qibojit,platform=numba,max_qubits=2 --nreps $nreps_cpu --precision $precision + echo + CUDA_VISIBLE_DEVICES="" python compare.py --circuit $circuit --nqubits $nqubits --filename $filename \ + --library-options backend=qibojit,platform=numba --nreps $nreps_cpu --precision $precision + echo +done diff --git a/qibojit-benchmarks/scripts/libraries_double.sh b/qibojit-benchmarks/scripts/libraries_double.sh new file mode 100755 index 0000000..a64e63e --- /dev/null +++ b/qibojit-benchmarks/scripts/libraries_double.sh @@ -0,0 +1,24 @@ +#! /usr/bin/bash +# Generates data for the bar plot comparing different libraries in double precision + +: "${nreps:=10}" +: "${nqubits:=10}" +: "${filename_cpu:=libraries_cpu.dat}" +: "${filename_gpu:=libraries_gpu.dat}" + + +for circuit in qft variational bv supremacy qv +do + for library in qibo qiskit qulacs projectq hybridq + do + CUDA_VISIBLE_DEVICES="" python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_cpu \ + --library $library --nreps $nreps --precision double + echo + done + for library in qibo qiskit-gpu qulacs-gpu hybridq-gpu + do + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_gpu \ + --library $library --nreps $nreps --precision double + echo + done +done diff --git a/qibojit-benchmarks/scripts/libraries_fusion.sh b/qibojit-benchmarks/scripts/libraries_fusion.sh new file mode 100755 index 0000000..348b217 --- /dev/null +++ b/qibojit-benchmarks/scripts/libraries_fusion.sh @@ -0,0 +1,42 @@ +#! /usr/bin/bash +# Generate logs for library comparison on different circuits with gate fusion enabled + +# Command-line parameters +: "${filename_cpu:=libraries_fusion_cpu.dat}" +: "${filename_gpu:=libraries_fusion_gpu.dat}" +: "${precision:=single}" +: "${nqubits:=30}" +: "${nreps_cpu:=3}" +: "${nreps_gpu:=5}" + + +for circuit in qft variational supremacy bv qv +do + # GPU benchmarks + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_gpu --library qibo \ + --library-options backend=qibojit,platform=cupy,max_qubits=2 --nreps $nreps_gpu --precision $precision + echo + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_gpu --library qibo \ + --library-options backend=qibojit,platform=cuquantum,max_qubits=2 --nreps $nreps_gpu --precision $precision + echo + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_gpu --library qsim-gpu \ + --library-options max_qubits=2 --nreps $nreps_gpu --precision $precision + echo + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_gpu --library qsim-cuquantum \ + --library-options max_qubits=2 --nreps $nreps_gpu --precision $precision + echo + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_gpu --library qiskit-gpu \ + --library-options max_qubits=2 --nreps $nreps_gpu --precision $precision + echo + + # CPU benchmarks + CUDA_VISIBLE_DEVICES="" python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_cpu --library qibo \ + --library-options backend=qibojit,platform=numba,max_qubits=2 --nreps $nreps_cpu --precision $precision + echo + CUDA_VISIBLE_DEVICES="" python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_cpu --library qsim \ + --library-options max_qubits=2 --nreps $nreps_cpu --precision $precision + echo + CUDA_VISIBLE_DEVICES="" python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_cpu --library qiskit \ + --library-options max_qubits=2 --nreps $nreps_cpu --precision $precision + echo +done diff --git a/qibojit-benchmarks/scripts/libraries_single.sh b/qibojit-benchmarks/scripts/libraries_single.sh new file mode 100755 index 0000000..0d4f148 --- /dev/null +++ b/qibojit-benchmarks/scripts/libraries_single.sh @@ -0,0 +1,24 @@ +#! /usr/bin/bash +# Generates data for the bar plot comparing different libraries in single precision + +: "${nreps:=10}" +: "${nqubits:=20}" +: "${filename_cpu:=libraries_cpu.dat}" +: "${filename_gpu:=libraries_gpu.dat}" + + +for circuit in qft variational bv supremacy qv +do + for library in qibo qiskit qsim hybridq + do + CUDA_VISIBLE_DEVICES="" python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_cpu \ + --library $library --nreps $nreps --precision single + echo + done + for library in qibo qiskit-gpu qsim-gpu qsim-cuquantum qcgpu hybridq-gpu + do + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_gpu \ + --library $library --nreps $nreps --precision single + echo + done +done diff --git a/qibojit-benchmarks/scripts/multigpu.sh b/qibojit-benchmarks/scripts/multigpu.sh new file mode 100755 index 0000000..03216e7 --- /dev/null +++ b/qibojit-benchmarks/scripts/multigpu.sh @@ -0,0 +1,34 @@ +# Generates the multigpu bar plot +# Assumes a machine with four GPUs + +: "${filename:=multigpu.dat}" +: "${precision:=double}" +: "${nreps:=1}" + +export CUDA_VISIBLE_DEVICES=0,1,2,3 +for circuit in qft variational supremacy qv bv +do + for backend in qibojit qibotf + do + python compare.py --circuit $circuit --nqubits 32 --filename $filename \ + --library-options backend=$backend,accelerators=1/GPU:0+1/GPU:1+1/GPU:2+1/GPU:3 \ + --nreps $nreps --precision $precision + echo + done + + for backend in qibojit qibotf + do + python compare.py --circuit $circuit --nqubits 32 --filename $filename \ + --library-options backend=$backend,accelerators=2/GPU:2+2/GPU:3 \ + --nreps $nreps --precision $precision + echo + done + + for backend in qibojit qibotf + do + python compare.py --circuit $circuit --nqubits 32 --filename $filename \ + --library-options backend=$backend,accelerators=4/GPU:3 \ + --nreps $nreps --precision $precision + echo + done +done \ No newline at end of file diff --git a/qibojit-benchmarks/scripts/qibo.sh b/qibojit-benchmarks/scripts/qibo.sh new file mode 100755 index 0000000..10b4f1e --- /dev/null +++ b/qibojit-benchmarks/scripts/qibo.sh @@ -0,0 +1,85 @@ +#! /usr/bin/bash +# Generates data for the scaling plot (vs nqubits) using all qibo backends + +# Command-line parameters +: "${circuit:=qft}" +: "${precision:=double}" +: "${nreps_a:=20}" # for nqubits < 25 +: "${nreps_b:=3}" # for nqubits >= 25 +: "${filename_cpu:=qibo_scaling_cpu.dat}" +: "${filename_gpu:=qibo_scaling_gpu.dat}" + + +# Qibojit backend +for nqubits in {3..24} +do + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_gpu \ + --library-options backend=qibojit,platform=cupy \ + --nreps $nreps_a --precision $precision + echo + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_gpu \ + --library-options backend=qibojit,platform=cuquantum \ + --nreps $nreps_a --precision $precision + echo + CUDA_VISIBLE_DEVICES="" python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_cpu \ + --library-options backend=qibojit,platform=numba \ + --nreps $nreps_a --precision $precision + echo +done +for nqubits in {25..31} +do + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_gpu \ + --library-options backend=qibojit,platform=cupy \ + --nreps $nreps_b --precision $precision + echo + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_gpu \ + --library-options backend=qibojit,platform=cuquantum \ + --nreps $nreps_b --precision $precision + echo + CUDA_VISIBLE_DEVICES="" python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_cpu \ + --library-options backend=qibojit,platform=numba \ + --nreps $nreps_b --precision $precision + echo +done + + +# TensorFlow and QiboTF backends +for nqubits in {3..24} +do + for backend in tensorflow qibotf + do + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_gpu \ + --library-options backend=$backend --nreps $nreps_a --precision $precision + echo + CUDA_VISIBLE_DEVICES="" python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_cpu \ + --library-options backend=$backend --nreps $nreps_a --precision $precision + echo + done +done +for nqubits in {25..31} +do + for backend in tensorflow qibotf + do + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_gpu \ + --library-options backend=$backend --nreps $nreps_b --precision $precision + echo + CUDA_VISIBLE_DEVICES="" python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_cpu \ + --library-options backend=$backend --nreps $nreps_b --precision $precision + echo + done +done + + +# NumPy backend +for nqubits in {3..24} +do + CUDA_VISIBLE_DEVICES="" python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_cpu \ + --library-options backend=numpy --nreps $nreps_a --precision $precision + echo +done +for nqubits in {25..28} +do + CUDA_VISIBLE_DEVICES="" python compare.py --circuit $circuit --nqubits $nqubits --filename $filename_cpu \ + --library-options backend=numpy --nreps $nreps_b --precision $precision + echo +done diff --git a/qibojit-benchmarks/scripts/qibojit.sh b/qibojit-benchmarks/scripts/qibojit.sh new file mode 100755 index 0000000..67531ff --- /dev/null +++ b/qibojit-benchmarks/scripts/qibojit.sh @@ -0,0 +1,23 @@ +#! /usr/bin/bash +# Generates data for qibojit breakdown bar plot with dry run vs simulation + +# Command-line parameters +: "${filename:=qibojit_breakdown.dat}" +: "${precision:=double}" +: "${circuit:=supremacy}" +: "${nreps_cpu:=5}" +: "${nreps_gpu:=10}" + + +for nqubits in 16 18 20 22 24 26 28 +do + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename \ + --library-options backend=qibojit,platform=cupy --nreps $nreps_gpu --precision $precision + echo + CUDA_VISIBLE_DEVICES=0 python compare.py --circuit $circuit --nqubits $nqubits --filename $filename \ + --library-options backend=qibojit,platform=cuquantum --nreps $nreps_gpu --precision $precision + echo + CUDA_VISIBLE_DEVICES="" python compare.py --circuit $circuit --nqubits $nqubits --filename $filename \ + --library-options backend=qibojit,platform=numba --nreps $nreps_cpu --precision $precision + echo +done diff --git a/qibojit-benchmarks/scripts/qibotn.sh b/qibojit-benchmarks/scripts/qibotn.sh new file mode 100755 index 0000000..378e134 --- /dev/null +++ b/qibojit-benchmarks/scripts/qibotn.sh @@ -0,0 +1,36 @@ +#! /usr/bin/bash +# Script for 2-nodes-4-GPUs (2x4) configuration +# Command-line parameters +: "${circuit:=variational}" +: "${precision:=double}" +: "${nreps:=3}" +: "${filename:=qibotn_expectation_double_2x4.dat}" + +node_list=/nodelist_2x4 #file containing the IP address of the resources, omit this if it is running on single-node-multi-gpu + +for (nqubits = 10; nqubits <= 8000; nqubits += 100) +do + #echo '{"MPI_enabled": true, "MPS_enabled": false, "NCCL_enabled": false, "expectation_enabled": true}' > cu_tensornet_mpi_expectation.json #can be pre-generated or can include it in script. Here is using pre-generated. + mpirun -np 8 -hostfile $node_list python compare.py --circuit variational --circuit-options nlayers=3 --nqubits 4$nqubits --filename $filename \ + --library-options backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_mpi_expectation.json \ + --nreps $nreps --precision $precision +echo + #echo '{"MPI_enabled": false, "MPS_enabled": false, "NCCL_enabled": true, "expectation_enabled": true}' > cu_tensornet_nccl_expectation.json + mpirun -np 8 -hostfile $node_list python compare.py --circuit variational --circuit-options nlayers=3 --nqubits $nqubits --filename $filename \ + --library-options backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_nccl_expectation.json \ + --nreps $nreps --precision $precision +echo + #echo '{"MPI_enabled": false, "MPS_enabled": false, "NCCL_enabled": false, "expectation_enabled": true}' > cu_tensornet_expectation.json + python compare.py --circuit variational --circuit-options nlayers=3 --nqubits $nqubits --filename $filename \ + --library-options backend=qibotn,platform=cutensornet,computation_settings=cu_tensornet_expectation.json \ + --nreps $nreps --precision $precision +echo + python compare.py --circuit variational --circuit-options nlayers=3 --nqubits $nqubits --filename $filename \ + --library-options backend=qibojit,platform=numba,expectation="XXXZ" \ + --nreps $nreps --precision $precision +echo +done + +: <<'END_COMMENT' + +END_COMMENT diff --git a/qibojit-benchmarks/scripts/qibotn_local.sh b/qibojit-benchmarks/scripts/qibotn_local.sh new file mode 100755 index 0000000..edfcc81 --- /dev/null +++ b/qibojit-benchmarks/scripts/qibotn_local.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Lightweight local benchmark script (single node, small sweep) +set -euo pipefail + +: "${circuit:=variational}" +: "${precision:=complex128}" +: "${nreps:=1}" +: "${filename:=qibotn_expectation_local.dat}" +: "${np:=2}" +: "${nlayers:=2}" +: "${nqubits_list:=8 12 16}" + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "${script_dir}/.." && pwd)" +cd "${repo_root}" + +# On Prism, Intel MPI may need shared-memory fabric for local runs. +if [[ -z "${I_MPI_FABRICS:-}" ]]; then + export I_MPI_FABRICS=shm +fi + +for nqubits in ${nqubits_list}; do + echo "===== nqubits=${nqubits} =====" + + # MPI-enabled cutensornet expectation + mpirun -np "${np}" python compare.py \ + --circuit "${circuit}" \ + --circuit-options "nlayers=${nlayers}" \ + --nqubits "${nqubits}" \ + --filename "${filename}" \ + --library-options backend=qibotn,platform=cutensornet,computation_settings=scripts/cu_tensornet_mpi_expectation.json \ + --nreps "${nreps}" \ + --precision "${precision}" + + # Single-process cutensornet expectation + python compare.py \ + --circuit "${circuit}" \ + --circuit-options "nlayers=${nlayers}" \ + --nqubits "${nqubits}" \ + --filename "${filename}" \ + --library-options backend=qibotn,platform=cutensornet,computation_settings=scripts/cu_tensornet_expectation.json \ + --nreps "${nreps}" \ + --precision "${precision}" + + # qibojit reference + python compare.py \ + --circuit "${circuit}" \ + --circuit-options "nlayers=${nlayers}" \ + --nqubits "${nqubits}" \ + --filename "${filename}" \ + --library-options backend=qibojit,platform=numba,expectation=XXXZ \ + --nreps "${nreps}" \ + --precision "${precision}" + + echo + done diff --git a/qibojit-benchmarks/scripts/quimb/README.md b/qibojit-benchmarks/scripts/quimb/README.md new file mode 100644 index 0000000..dcf9069 --- /dev/null +++ b/qibojit-benchmarks/scripts/quimb/README.md @@ -0,0 +1,214 @@ +# Quimb Benchmark Suite + +Local (single-process) tensor network simulation benchmarking using quimb backend. + +## Files + +- **quimb_local.sh** — Main benchmark sweep script (no MPI) +- **expectation_mps.json** — Expectation mode with MPS (default for expectation) +- **expectation_dense.json** — Expectation mode, dense tensor network +- **dense_vector_mps.json** — State extraction with MPS (default for state) +- **dense_vector_dense.json** — State extraction, dense tensor network + +## Configuration Files + +### Expectation Modes (computing Hamiltonian expectation values) +- **expectation_mps.json**: Uses Matrix Product State (MPS) tensor network ansatz; good for larger systems with limited bond dimension +- **expectation_dense.json**: Uses dense tensor network; full contraction without MPS truncation + +### State Modes (full state vector computation) +- **dense_vector_mps.json**: Extracts full state using MPS contraction (faster, bounded memory) +- **dense_vector_dense.json**: Extracts full state using dense tensor (maximum memory usage) + +## Usage + +```bash +cd /ssd_data/tankya2/code/ASC_2026_prism/ASC-2026/qibojit-benchmarks/scripts/quimb + +# Run with defaults (all matching dense_vector*.json and expectation*.json configs) +./quimb_local.sh + +# Test only expectation mode, custom qubit sizes +modes="expectation" nqubits_list="8 10 12" ./quimb_local.sh + +# Test dense vector only with one explicit config +state_cfg=dense_vector_dense.json modes="dense_vector" ./quimb_local.sh + +# Restrict the sweep to a chosen subset of configs +EXPECTATION_CONFIGS="expectation_mps.json expectation_dense.json" ./quimb_local.sh + +# Test variational and BV circuits +circuits="variational bv" nqubits_list="6 8 10" nreps=3 ./quimb_local.sh +``` + +## Supported Circuits + +### ✓ Fully Supported (Numerically Validated) + +| Circuit | Alias | Parameters | Status | Notes | +|---------|-------|------------|--------|-------| +| **qft** | — | none | ✓ Works | Quantum Fourier Transform; aligned with qibojit | +| **supremacy** | — | depth, seed | ✓ Works | Verified locally after environment repair; requires Cirq import path to be healthy | +| **variational** | — | nlayers | ✓ Works | Parameterized circuit; aligned with qibojit | +| **bv** | bernstein-vazirani | none | ✓ Works | Bernstein-Vazirani algorithm; aligned with qibojit | +| **hs** | hidden-shift | none | ✓ Works | Hidden-shift problem; aligned with qibojit | +| **qaoa** | — | nlayers | ✓ Works | QAOA with RZZ gate support; aligned with qibojit | + +### ❌ Not Supported + +| Circuit | Issue | Notes | +|---------|-------|-------| +| **qv** | Qiskit API compatibility | quantum-volume uses deprecated qiskit `.qasm()` method (v3.0+); not specific to quimb | + +### Implementation Notes +- **RZZ Gate:** Full support for QAOA and algorithms using two-qubit Ising rotations +- **cu1 Gate:** Fully supported in expectation value mode (QFT compatible) +- **Numerical Alignment:** All benchmarks automatically include qibojit (numba) reference with identical observable for validation + +## Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `nqubits_list` | `6 8 10` | Space-separated qubit counts to benchmark | +| `circuits` | `supremacy qft variational bv qaoa` | Space-separated circuit names | +| `modes` | `dense_vector expectation` | Benchmark modes (dense_vector, expectation) | +| `nlayers` | `2` | Layers for layered circuits (variational, qaoa) | +| `nreps` | `1` | Repetitions per benchmark | +| `filename` | `quimb_benchmark_local.dat` | Output log file | +| `precision` | `complex128` | NumPy dtype (complex128 or complex64) | +| `exp_cfg` | unset | Optional single expectation config; when unset, all `expectation*.json` files are swept | +| `state_cfg` | unset | Optional single dense-vector config; when unset, all `dense_vector*.json` files are swept | +| `EXPECTATION_CONFIGS` | unset | Optional space-separated subset of expectation configs to run | +| `STATE_CONFIGS` | unset | Optional space-separated subset of dense-vector configs to run | + +## Results + +Results are logged to `/qibojit-benchmarks/benchmarks/results/` with filename `quimb_benchmark_local.dat` (configurable). + +Each benchmark includes: +- **import_time** — Backend initialization time +- **creation_time** — Circuit creation time +- **dry_run_time** — First execution time (warm-up) +- **simulation_times** — Execution times for nreps runs +- **expectation_result** — Observable expectation values (for expectation mode) + +## Observable + +By default, expectation uses: +- **X on qubit 0**, identity on remaining qubits +- Configurable via `pauli_pattern` in JSON config (e.g., `"XIIII"` for 5 qubits) + +## Reference Validation + +Each benchmark run automatically includes a **qibojit (numba) reference** with the same observable configuration, enabling direct numerical comparison for validation. + +Example: +``` +[expectation MPI] quimb + qft @ 6q: 0.2019... +[qibojit ref] numba + qft @ 6q: 0.2019... ✓ aligned +``` + +## MPI Support + +For MPI-enabled benchmarking (multiple processes), see the parallel suite in `/scripts/quimb_mpi/`: + +```bash +cd /ssd_data/tankya2/code/ASC_2026_prism/ASC-2026/qibojit-benchmarks/scripts/quimb_mpi +./quimb_mpi.sh # runs with mpirun -np 2 +``` + +## Tensor Network Options + +Quimb supports both MPS (Matrix Product State) and dense tensor network contractions. By default the script sweeps every matching JSON file in this folder, or you can target individual configs: + +```bash +# Compare MPS vs dense at same nqubits +exp_cfg=expectation_mps.json nqubits_list="12" ./quimb_local.sh +exp_cfg=expectation_dense.json nqubits_list="12" ./quimb_local.sh +``` + +MPS is typically faster and uses bounded memory, while dense provides full accuracy without truncation. + +## Installation + +Preferred path: create a fresh environment from the benchmark spec. + +```bash +cd /ssd_data/tankya2/code/ASC_2026_prism/ASC-2026/qibojit-benchmarks +conda env create -f environment.yml +conda activate benchmarks +``` + +If you are using an existing environment instead of recreating it, check that it is aligned with the benchmark stack: + +```bash +python -c "import numpy, qibo, qibojit; print('numpy', numpy.__version__); print('qibo', qibo.__version__); print('qibojit', qibojit.__version__)" +``` + +## Dependency Recovery + +```bash +pip install cirq==0.8.2 +``` +This is the recovery path that eventually made `./quimb_local.sh` work for `supremacy` in the existing `qibotn-py311-test` environment. + +### Symptom 1: `cirq` failed with `ModuleNotFoundError: No module named 'pkg_resources'` + +Cause: +- the existing environment had `setuptools 82.0.1` +- that install did not expose `pkg_resources` +- the installed `cirq` package still imported `pkg_resources` during startup + +Fix: + +```bash +/home/tankya2/miniforge3/envs/qibotn-py311-test/bin/python -m pip install "setuptools<81" +``` + +Working result: +- `setuptools` was changed to `80.10.2` +- `import pkg_resources` worked again +- the Quimb `supremacy` path could construct the Cirq-generated circuit + +### Symptom 2: `qibojit` reference crashed with `TypeError: asarray() got an unexpected keyword argument 'copy'` + +Cause: +- the same environment had `numpy 1.26.4` +- but `qibo 0.3.1` requires `numpy >= 2.0.0` +- the `qibojit` reference backend was therefore running against an incompatible NumPy version + +Fix: + +```bash +/home/tankya2/miniforge3/envs/qibotn-py311-test/bin/python -m pip install numpy==2.2.6 +``` + +Working result: +- `numpy` was changed to `2.2.6` +- the `qibojit` reference backend started working again +- both the Quimb and qibojit sides of the `supremacy` benchmark completed successfully + +### Final verified state + +The existing repaired environment was verified with: + +```bash +cd /ssd_data/tankya2/code/ASC_2026_prism/ASC-2026/qibojit-benchmarks/scripts/quimb +nqubits_list="6" circuits="supremacy" nreps=1 modes="dense_vector expectation" ./quimb_local.sh +``` + +That smoke test completed all three stages: +- Quimb `dense_vector` +- Quimb `expectation` +- qibojit reference + +Notes: +- `environment.yml` remains the preferred source of truth for a clean install +- the repaired environment may still emit a `pkg_resources` deprecation warning from the older Cirq stack, but the benchmark run succeeds +- if an environment has drifted far from `environment.yml`, recreating it is usually safer than incremental repair + +## Notes + +- **Intel MPI Fabric:** On Prism cluster, shared-memory fabric (`I_MPI_FABRICS=shm`) is automatically enabled for single-node runs +- **QFT Support:** Fixed cu1 gate compatibility in quimb backend; no platform-specific gate limitations +- **Bond Dimension:** MPS modes use max_bond_dimension=20 by default; adjust in JSON for memory/accuracy tradeoff diff --git a/qibojit-benchmarks/scripts/quimb/dense_vector_dense.json b/qibojit-benchmarks/scripts/quimb/dense_vector_dense.json new file mode 100644 index 0000000..ef38ac1 --- /dev/null +++ b/qibojit-benchmarks/scripts/quimb/dense_vector_dense.json @@ -0,0 +1 @@ +{"expectation_enabled": false, "use_mps": false} diff --git a/qibojit-benchmarks/scripts/quimb/dense_vector_mps.json b/qibojit-benchmarks/scripts/quimb/dense_vector_mps.json new file mode 100644 index 0000000..89d99f7 --- /dev/null +++ b/qibojit-benchmarks/scripts/quimb/dense_vector_mps.json @@ -0,0 +1 @@ +{"expectation_enabled": false, "use_mps": true, "max_bond_dimension": 20, "svd_cutoff": 1e-10} \ No newline at end of file diff --git a/qibojit-benchmarks/scripts/quimb/expectation_dense.json b/qibojit-benchmarks/scripts/quimb/expectation_dense.json new file mode 100644 index 0000000..09af358 --- /dev/null +++ b/qibojit-benchmarks/scripts/quimb/expectation_dense.json @@ -0,0 +1 @@ +{"expectation_enabled": true, "use_mps": false, "pauli_pattern": "XIIII"} diff --git a/qibojit-benchmarks/scripts/quimb/expectation_dense_auto.json b/qibojit-benchmarks/scripts/quimb/expectation_dense_auto.json new file mode 100644 index 0000000..c422e1d --- /dev/null +++ b/qibojit-benchmarks/scripts/quimb/expectation_dense_auto.json @@ -0,0 +1,6 @@ +{ + "expectation_enabled": true, + "use_mps": false, + "pauli_pattern_auto": true, + "pauli_pattern_style": "mixed" +} diff --git a/qibojit-benchmarks/scripts/quimb/expectation_mps.json b/qibojit-benchmarks/scripts/quimb/expectation_mps.json new file mode 100644 index 0000000..6996f36 --- /dev/null +++ b/qibojit-benchmarks/scripts/quimb/expectation_mps.json @@ -0,0 +1 @@ +{"expectation_enabled": true, "use_mps": true, "max_bond_dimension": 20, "svd_cutoff": 1e-10, "pauli_pattern": "XIIII"} \ No newline at end of file diff --git a/qibojit-benchmarks/scripts/quimb/quimb_local.sh b/qibojit-benchmarks/scripts/quimb/quimb_local.sh new file mode 100755 index 0000000..bc63478 --- /dev/null +++ b/qibojit-benchmarks/scripts/quimb/quimb_local.sh @@ -0,0 +1,215 @@ +#!/usr/bin/env bash +# Local quimb benchmark sweep. +# Tests both dense vector (non-expectation) and expectation across multiple +# qubit sizes and circuit types. +# +# Usage examples: +# ./quimb_local.sh +# nqubits_list="6 8 10" circuits="variational bv" ./quimb_local.sh +# modes="expectation" nreps=3 ./quimb_local.sh +# state_cfg=dense_vector_dense.json modes="dense_vector" ./quimb_local.sh +set -euo pipefail + +: "${precision:=complex128}" +: "${nreps:=3}" +: "${filename:=quimb_benchmark_local.dat}" +: "${nlayers:=2}" +: "${nqubits_list:=6 8 10}" +: "${circuits:=supremacy qft variational qaoa}" +: "${modes:=dense_vector expectation}" +: "${exp_cfg:=}" +: "${state_cfg:=}" +: "${EXPECTATION_CONFIGS:=}" +: "${STATE_CONFIGS:=}" + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "${script_dir}/../.." && pwd)" +cd "${repo_root}" + +config_list_from_prefix() { + local prefix="$1" + local single_cfg="$2" + local override_list="$3" + + if [[ -n "${override_list}" ]]; then + for cfg in ${override_list}; do + printf '%s\n' "${cfg}" + done + return + fi + + if [[ -n "${single_cfg}" ]]; then + printf '%s\n' "${single_cfg}" + return + fi + + shopt -s nullglob + local matches=( "${script_dir}/${prefix}"*.json ) + shopt -u nullglob + + for path in "${matches[@]}"; do + basename "${path}" + done +} + +require_existing_configs() { + local mode="$1" + shift + + for cfg in "$@"; do + if [[ ! -f "${script_dir}/${cfg}" ]]; then + echo "Missing ${mode} config: ${script_dir}/${cfg}" >&2 + exit 1 + fi + done +} + +read_pauli_pattern() { + local cfg_path="$1" + + python - "${cfg_path}" <<'PY' +import json +import sys + +try: + with open(sys.argv[1], "r") as f: + data = json.load(f) + print(data.get("pauli_pattern", "")) +except Exception: + print("") +PY +} + +build_ref_pattern() { + local base_pauli_pattern="$1" + local nqubits="$2" + + python - "${base_pauli_pattern}" "${nqubits}" <<'PY' +import sys + +p = sys.argv[1].upper() +n = int(sys.argv[2]) + +if not p: + p = "X" + "I" * max(n - 1, 0) +if len(p) < n: + p = p + ("I" * (n - len(p))) +else: + p = p[:n] +print(p) +PY +} + +run_qibojit_reference() { + local nqubits="$1" + local circuit="$2" + local circuit_opts="$3" + local exp_cfg_name="$4" + local exp_cfg_path="${script_dir}/${exp_cfg_name}" + local base_pauli_pattern + local ref_pattern + + base_pauli_pattern="$(read_pauli_pattern "${exp_cfg_path}")" + ref_pattern="$(build_ref_pattern "${base_pauli_pattern}" "${nqubits}")" + + echo " [qibojit reference] cfg=${exp_cfg_name}" + python compare.py \ + --circuit "${circuit}" \ + ${circuit_opts:+--circuit-options "${circuit_opts}"} \ + --nqubits "${nqubits}" \ + --filename "${filename}" \ + --library-options backend=qibojit,platform=numba,expectation=${ref_pattern} \ + --nreps "${nreps}" \ + --precision "${precision}" +} + +mapfile -t expectation_cfgs < <( + config_list_from_prefix "expectation" "${exp_cfg}" "${EXPECTATION_CONFIGS}" +) +mapfile -t state_cfgs < <( + config_list_from_prefix "dense_vector" "${state_cfg}" "${STATE_CONFIGS}" +) + +run_dense_mode=false +run_expectation_mode=false + +if echo "${modes}" | grep -Eqw "dense_vector|statevector"; then + run_dense_mode=true +fi + +if echo "${modes}" | grep -qw "expectation"; then + run_expectation_mode=true +fi + +if [[ "${run_dense_mode}" == true && ${#state_cfgs[@]} -eq 0 ]]; then + echo "No dense-vector JSON configs found in ${script_dir}" >&2 + exit 1 +fi + +if [[ "${run_expectation_mode}" == true && ${#expectation_cfgs[@]} -eq 0 ]]; then + echo "No expectation JSON configs found in ${script_dir}" >&2 + exit 1 +fi + +require_existing_configs "dense-vector" "${state_cfgs[@]}" +require_existing_configs "expectation" "${expectation_cfgs[@]}" + +# On Prism, Intel MPI needs shared-memory fabric for single-node runs. +if [[ -z "${I_MPI_FABRICS:-}" ]]; then + export I_MPI_FABRICS=shm +fi + +echo "Dense-vector configs : ${state_cfgs[*]:-none}" +echo "Expectation configs : ${expectation_cfgs[*]:-none}" + +for nqubits in ${nqubits_list}; do + for circuit in ${circuits}; do + circuit_opts="nlayers=${nlayers}" + # These circuits do not use nlayers — omit it to avoid unknown-option errors. + if [[ "${circuit}" == "qft" || "${circuit}" == "QFT" || \ + "${circuit}" == "supremacy" || "${circuit}" == "Supremacy" || \ + "${circuit}" == "bv" || "${circuit}" == "bernstein-vazirani" || \ + "${circuit}" == "hs" || "${circuit}" == "hidden-shift" || \ + "${circuit}" == "qaoa" || "${circuit}" == "qv" || \ + "${circuit}" == "quantum-volume" ]]; then + circuit_opts="" + fi + + echo "===== nqubits=${nqubits} circuit=${circuit} =====" + + # dense_vector is the preferred name; statevector is kept as a legacy alias. + if [[ "${run_dense_mode}" == true ]]; then + for cfg_name in "${state_cfgs[@]}"; do + state_cfg_path="${script_dir}/${cfg_name}" + echo " [dense_vector] cfg=${cfg_name}" + python compare.py \ + --circuit "${circuit}" \ + ${circuit_opts:+--circuit-options "${circuit_opts}"} \ + --nqubits "${nqubits}" \ + --filename "${filename}" \ + --library-options backend=qibotn,platform=quimb,computation_settings=${state_cfg_path} \ + --nreps "${nreps}" \ + --precision "${precision}" + done + fi + + if [[ "${run_expectation_mode}" == true ]]; then + for cfg_name in "${expectation_cfgs[@]}"; do + exp_cfg_path="${script_dir}/${cfg_name}" + echo " [expectation] cfg=${cfg_name}" + python compare.py \ + --circuit "${circuit}" \ + ${circuit_opts:+--circuit-options "${circuit_opts}"} \ + --nqubits "${nqubits}" \ + --filename "${filename}" \ + --library-options backend=qibotn,platform=quimb,computation_settings=${exp_cfg_path} \ + --nreps "${nreps}" \ + --precision "${precision}" + + run_qibojit_reference "${nqubits}" "${circuit}" "${circuit_opts}" "${cfg_name}" + done + fi + + echo + done +done diff --git a/qibojit-benchmarks/scripts/quimb_mpi/README.md b/qibojit-benchmarks/scripts/quimb_mpi/README.md new file mode 100644 index 0000000..51addde --- /dev/null +++ b/qibojit-benchmarks/scripts/quimb_mpi/README.md @@ -0,0 +1,167 @@ +# Quimb MPI Benchmark Suite + +Demonstrates MPI-enabled tensor network simulation using quimb with 2 MPI processes. + +## Files + +- **quimb_mpi.sh** — Main benchmark sweep script using `mpirun -np 2` +- **expectation_mps_mpi.json** — Expectation mode with MPS (default for expectation) +- **expectation_dense_mpi.json** — Expectation mode, dense tensor network +- **dense_vector_mps_mpi.json** — State extraction with MPS (default for state) +- **dense_vector_dense_mpi.json** — State extraction, dense tensor network + +## Key Differences from `/scripts/quimb/` + +- Each `python compare.py` call wrapped with `mpirun -np 2` to enable MPI parallelization +- All JSON configs include `"MPI_enabled": true` +- Default file names include `_mpi` suffix to distinguish from single-process configs +- Filenames in logs clearly show MPI mode (e.g., `expectation_mps_mpi.json`) + +## Configuration Files + +### Expectation Modes (computing Hamiltonian expectation values) +- **expectation_mps_mpi.json**: MPI-enabled, uses Matrix Product State (MPS) tensor network ansatz; good for larger systems with limited bond dimension +- **expectation_dense_mpi.json**: MPI-enabled, uses dense tensor network; full contraction without MPS truncation + +### State Modes (full state vector computation) +- **dense_vector_mps_mpi.json**: MPI-enabled, extracts full state using MPS contraction (faster, bounded memory) +- **dense_vector_dense_mpi.json**: MPI-enabled, extracts full state using dense tensor (maximum memory usage) + +## Usage + +```bash +cd /ssd_data/tankya2/code/ASC_2026_prism/ASC-2026/qibojit-benchmarks/scripts/quimb_mpi + +# Run with defaults (all matching dense_vector*.json and expectation*.json configs) +./quimb_mpi.sh + +# Test only expectation mode, custom qubit sizes +modes="expectation" nqubits_list="8 10 12" ./quimb_mpi.sh + +# Test dense vector only with one explicit config +state_cfg=dense_vector_dense_mpi.json modes="dense_vector" ./quimb_mpi.sh + +# Restrict the sweep to a chosen subset of configs +EXPECTATION_CONFIGS="expectation_mps_mpi.json expectation_dense_mpi.json" ./quimb_mpi.sh + +# Use 4 MPI processes (if testing scalability) +np=4 ./quimb_mpi.sh +``` + +## Supported Circuits + +### ✓ Fully Supported (Numerically Validated) + +| Circuit | Alias | Parameters | Status | Notes | +|---------|-------|------------|--------|-------| +| **qft** | — | none | ✓ Works | Quantum Fourier Transform; aligned with qibojit | +| **variational** | — | nlayers | ✓ Works | Parameterized circuit; aligned with qibojit | +| **bv** | bernstein-vazirani | none | ✓ Works | Bernstein-Vazirani algorithm; aligned with qibojit | +| **hs** | hidden-shift | none | ✓ Works | Hidden-shift problem; aligned with qibojit | +| **qaoa** | — | nlayers | ✓ Works | QAOA with RZZ gate support; aligned with qibojit | + +### ❌ Not Supported + +| Circuit | Issue | Notes | +|---------|-------|-------| +| **qv** | Qiskit API compatibility | quantum-volume uses deprecated qiskit `.qasm()` method (v3.0+); not specific to quimb | + +## Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `nqubits_list` | `6 8 10` | Space-separated qubit counts to benchmark | +| `circuits` | `qaoa` | Space-separated circuit names | +| `modes` | `dense_vector expectation` | Benchmark modes (dense_vector, expectation) | +| `nlayers` | `2` | Layers for layered circuits (variational, qaoa) | +| `np` | `2` | Number of MPI processes (mpirun -np) | +| `nreps` | `1` | Repetitions per benchmark | +| `filename` | `quimb_benchmark_mpi.dat` | Output log file | +| `precision` | `complex128` | NumPy dtype (complex128 or complex64) | +| `exp_cfg` | unset | Optional single expectation config; when unset, all `expectation*.json` files are swept | +| `state_cfg` | unset | Optional single dense-vector config; when unset, all `dense_vector*.json` files are swept | +| `EXPECTATION_CONFIGS` | unset | Optional space-separated subset of expectation configs to run | +| `STATE_CONFIGS` | unset | Optional space-separated subset of dense-vector configs to run | + +## Results + +Results are stored in `/qibojit-benchmarks/benchmarks/results/` with filename prefix `quimb_benchmark_mpi.dat` by default (configurable via `filename=` env var). + +Each benchmark includes: +- **import_time** — Backend initialization time +- **creation_time** — Circuit creation time +- **dry_run_time** — First execution time (warm-up) +- **simulation_times** — Execution times for nreps runs +- **expectation_result** — Observable expectation values (for expectation mode) + +## Observable + +By default, expectation uses: +- **X on qubit 0**, identity on remaining qubits +- Configurable via `pauli_pattern` in JSON config (e.g., `"XIIII"` for 5 qubits) + +## Reference Validation + +Each benchmark run automatically includes a **qibojit (numba) reference** with the same observable configuration, enabling direct numerical comparison for validation. + +Example: +``` +[expectation MPI] quimb + variational @ 6q: 0.2019... +[qibojit ref] numba + variational @ 6q: 0.2019... ✓ aligned +``` + +## Supported Circuits + +### ✓ Fully Supported (Numerically Validated) + +| Circuit | Alias | Parameters | Status | Notes | +|---------|-------|------------|--------|-------| +| **qft** | — | none | ✓ Works | Quantum Fourier Transform; aligned with qibojit | +| **variational** | — | nlayers | ✓ Works | Parameterized circuit; aligned with qibojit | +| **bv** | bernstein-vazirani | none | ✓ Works | Bernstein-Vazirani algorithm; aligned with qibojit | +| **hs** | hidden-shift | none | ✓ Works | Hidden-shift problem; aligned with qibojit | +| **qaoa** | — | nlayers | ✓ Works | QAOA with RZZ gate support; aligned with qibojit | + +### ❌ Not Supported + +| Circuit | Issue | Notes | +|---------|-------|-------| +| **qv** | Qiskit API compatibility | quantum-volume uses deprecated qiskit `.qasm()` method (v3.0+); not specific to quimb | + +## Notes + +- **Intel MPI Fabric:** On Prism, automatically sets `I_MPI_FABRICS=shm` (shared-memory fabric) for single-node local runs +- **Observable:** X on first qubit, identity on rest (configurable via `pauli_pattern` in JSON) +- **Reference:** qibojit (numba backend) included automatically with aligned observable for validation +- **RZZ Gate:** Full support for QAOA and algorithms using two-qubit Ising rotations +- **cu1 Gate:** Fully supported in expectation value mode (QFT compatible) +- **Numerical Alignment:** All benchmarks automatically include qibojit reference with identical observable for validation + +## MPI Enable/Disable + +To test without MPI, use configs from `/scripts/quimb/` instead: + +```bash +# Compare: MPI vs no MPI +./quimb_mpi.sh # with MPI (2 processes) +cd ../quimb && ./quimb_local.sh # without MPI (single process) +``` + +## Tensor Network Options + +Quimb supports both MPS (Matrix Product State) and dense tensor network contractions. By default the script sweeps every matching JSON file in this folder, or you can target individual configs: + +```bash +# Compare MPS vs dense at same nqubits with MPI +exp_cfg=expectation_mps_mpi.json nqubits_list="12" ./quimb_mpi.sh +exp_cfg=expectation_dense_mpi.json nqubits_list="12" ./quimb_mpi.sh +``` + +MPS is typically faster and uses bounded memory, while dense provides full accuracy without truncation. + +## Additional Notes + +- **Bond Dimension:** MPS modes use max_bond_dimension=20 by default; adjust in JSON for memory/accuracy tradeoff +- **SVD Cutoff:** Set to 1e-10 for high accuracy; increase for faster execution with acceptable loss tolerance +- **Scaling:** MPI parallelization enables larger systems; test with np=4, np=8, etc. for scaling studies +- **Single-Node MPI:** Default `-np 2` demonstrates MPI capability on local machine; for cluster runs, adjust `-np` and node configuration diff --git a/qibojit-benchmarks/scripts/quimb_mpi/dense_vector_dense_mpi.json b/qibojit-benchmarks/scripts/quimb_mpi/dense_vector_dense_mpi.json new file mode 100644 index 0000000..8f09ee5 --- /dev/null +++ b/qibojit-benchmarks/scripts/quimb_mpi/dense_vector_dense_mpi.json @@ -0,0 +1 @@ +{"MPI_enabled": true, "expectation_enabled": false, "use_mps": false} diff --git a/qibojit-benchmarks/scripts/quimb_mpi/dense_vector_mps_mpi.json b/qibojit-benchmarks/scripts/quimb_mpi/dense_vector_mps_mpi.json new file mode 100644 index 0000000..7730592 --- /dev/null +++ b/qibojit-benchmarks/scripts/quimb_mpi/dense_vector_mps_mpi.json @@ -0,0 +1 @@ +{"MPI_enabled": true, "expectation_enabled": false, "use_mps": true, "max_bond_dimension": 20, "svd_cutoff": 1e-10} diff --git a/qibojit-benchmarks/scripts/quimb_mpi/expectation_dense_mpi.json b/qibojit-benchmarks/scripts/quimb_mpi/expectation_dense_mpi.json new file mode 100644 index 0000000..8fae9c4 --- /dev/null +++ b/qibojit-benchmarks/scripts/quimb_mpi/expectation_dense_mpi.json @@ -0,0 +1 @@ +{"MPI_enabled": true, "expectation_enabled": true, "use_mps": false, "pauli_pattern": "XIIII"} diff --git a/qibojit-benchmarks/scripts/quimb_mpi/expectation_mps_mpi.json b/qibojit-benchmarks/scripts/quimb_mpi/expectation_mps_mpi.json new file mode 100644 index 0000000..0ff6534 --- /dev/null +++ b/qibojit-benchmarks/scripts/quimb_mpi/expectation_mps_mpi.json @@ -0,0 +1 @@ +{"MPI_enabled": true, "expectation_enabled": true, "use_mps": true, "max_bond_dimension": 20, "svd_cutoff": 1e-10, "pauli_pattern": "XIIII"} diff --git a/qibojit-benchmarks/scripts/quimb_mpi/quimb_mpi.sh b/qibojit-benchmarks/scripts/quimb_mpi/quimb_mpi.sh new file mode 100755 index 0000000..4a253fe --- /dev/null +++ b/qibojit-benchmarks/scripts/quimb_mpi/quimb_mpi.sh @@ -0,0 +1,216 @@ +#!/usr/bin/env bash +# MPI-enabled quimb benchmark sweep (2 processes). +# Tests both dense vector (non-expectation) and expectation across multiple +# qubit sizes and circuit types, using mpirun -np 2 to demonstrate MPI capability. +# +# Usage examples: +# ./quimb_mpi.sh +# nqubits_list="6 8 10" circuits="variational bv" ./quimb_mpi.sh +# modes="expectation" nreps=3 ./quimb_mpi.sh +# state_cfg=dense_vector_dense_mpi.json modes="dense_vector" ./quimb_mpi.sh +set -euo pipefail + +: "${precision:=complex128}" +: "${nreps:=3}" +: "${filename:=quimb_benchmark_mpi.dat}" +: "${nlayers:=2}" +: "${nqubits_list:=6 8 10}" +: "${circuits:=supremacy qft variational qaoa}" +: "${modes:=dense_vector expectation}" +: "${np:=2}" +: "${exp_cfg:=}" +: "${state_cfg:=}" +: "${EXPECTATION_CONFIGS:=}" +: "${STATE_CONFIGS:=}" + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "${script_dir}/../.." && pwd)" +cd "${repo_root}" + +config_list_from_prefix() { + local prefix="$1" + local single_cfg="$2" + local override_list="$3" + + if [[ -n "${override_list}" ]]; then + for cfg in ${override_list}; do + printf '%s\n' "${cfg}" + done + return + fi + + if [[ -n "${single_cfg}" ]]; then + printf '%s\n' "${single_cfg}" + return + fi + + shopt -s nullglob + local matches=( "${script_dir}/${prefix}"*.json ) + shopt -u nullglob + + for path in "${matches[@]}"; do + basename "${path}" + done +} + +require_existing_configs() { + local mode="$1" + shift + + for cfg in "$@"; do + if [[ ! -f "${script_dir}/${cfg}" ]]; then + echo "Missing ${mode} config: ${script_dir}/${cfg}" >&2 + exit 1 + fi + done +} + +read_pauli_pattern() { + local cfg_path="$1" + + python - "${cfg_path}" <<'PY' +import json +import sys + +try: + with open(sys.argv[1], "r") as f: + data = json.load(f) + print(data.get("pauli_pattern", "")) +except Exception: + print("") +PY +} + +build_ref_pattern() { + local base_pauli_pattern="$1" + local nqubits="$2" + + python - "${base_pauli_pattern}" "${nqubits}" <<'PY' +import sys + +p = sys.argv[1].upper() +n = int(sys.argv[2]) + +if not p: + p = "X" + "I" * max(n - 1, 0) +if len(p) < n: + p = p + ("I" * (n - len(p))) +else: + p = p[:n] +print(p) +PY +} + +run_qibojit_reference() { + local nqubits="$1" + local circuit="$2" + local circuit_opts="$3" + local exp_cfg_name="$4" + local exp_cfg_path="${script_dir}/${exp_cfg_name}" + local base_pauli_pattern + local ref_pattern + + base_pauli_pattern="$(read_pauli_pattern "${exp_cfg_path}")" + ref_pattern="$(build_ref_pattern "${base_pauli_pattern}" "${nqubits}")" + + echo " [qibojit reference] cfg=${exp_cfg_name}" + python compare.py \ + --circuit "${circuit}" \ + ${circuit_opts:+--circuit-options "${circuit_opts}"} \ + --nqubits "${nqubits}" \ + --filename "${filename}" \ + --library-options backend=qibojit,platform=numba,expectation=${ref_pattern} \ + --nreps "${nreps}" \ + --precision "${precision}" +} + +mapfile -t expectation_cfgs < <( + config_list_from_prefix "expectation" "${exp_cfg}" "${EXPECTATION_CONFIGS}" +) +mapfile -t state_cfgs < <( + config_list_from_prefix "dense_vector" "${state_cfg}" "${STATE_CONFIGS}" +) + +run_dense_mode=false +run_expectation_mode=false + +if echo "${modes}" | grep -Eqw "dense_vector|statevector"; then + run_dense_mode=true +fi + +if echo "${modes}" | grep -qw "expectation"; then + run_expectation_mode=true +fi + +if [[ "${run_dense_mode}" == true && ${#state_cfgs[@]} -eq 0 ]]; then + echo "No dense-vector JSON configs found in ${script_dir}" >&2 + exit 1 +fi + +if [[ "${run_expectation_mode}" == true && ${#expectation_cfgs[@]} -eq 0 ]]; then + echo "No expectation JSON configs found in ${script_dir}" >&2 + exit 1 +fi + +require_existing_configs "dense-vector" "${state_cfgs[@]}" +require_existing_configs "expectation" "${expectation_cfgs[@]}" + +# On Prism, Intel MPI needs shared-memory fabric for single-node runs. +if [[ -z "${I_MPI_FABRICS:-}" ]]; then + export I_MPI_FABRICS=shm +fi + +echo "Dense-vector configs : ${state_cfgs[*]:-none}" +echo "Expectation configs : ${expectation_cfgs[*]:-none}" + +for nqubits in ${nqubits_list}; do + for circuit in ${circuits}; do + circuit_opts="nlayers=${nlayers}" + # These circuits do not use nlayers — omit it to avoid unknown-option errors. + if [[ "${circuit}" == "qft" || "${circuit}" == "QFT" || \ + "${circuit}" == "supremacy" || "${circuit}" == "Supremacy" || \ + "${circuit}" == "bv" || "${circuit}" == "bernstein-vazirani" || \ + "${circuit}" == "hs" || "${circuit}" == "hidden-shift" || \ + "${circuit}" == "qaoa" || "${circuit}" == "qv" || \ + "${circuit}" == "quantum-volume" ]]; then + circuit_opts="" + fi + + echo "===== nqubits=${nqubits} circuit=${circuit} np=${np} =====" + + # dense_vector is the preferred name; statevector is kept as a legacy alias. + if [[ "${run_dense_mode}" == true ]]; then + for cfg_name in "${state_cfgs[@]}"; do + state_cfg_path="${script_dir}/${cfg_name}" + echo " [dense_vector MPI] cfg=${cfg_name}" + mpirun -np "${np}" python compare.py \ + --circuit "${circuit}" \ + ${circuit_opts:+--circuit-options "${circuit_opts}"} \ + --nqubits "${nqubits}" \ + --filename "${filename}" \ + --library-options backend=qibotn,platform=quimb,computation_settings=${state_cfg_path} \ + --nreps "${nreps}" \ + --precision "${precision}" + done + fi + + if [[ "${run_expectation_mode}" == true ]]; then + for cfg_name in "${expectation_cfgs[@]}"; do + exp_cfg_path="${script_dir}/${cfg_name}" + echo " [expectation MPI] cfg=${cfg_name}" + mpirun -np "${np}" python compare.py \ + --circuit "${circuit}" \ + ${circuit_opts:+--circuit-options "${circuit_opts}"} \ + --nqubits "${nqubits}" \ + --filename "${filename}" \ + --library-options backend=qibotn,platform=quimb,computation_settings=${exp_cfg_path} \ + --nreps "${nreps}" \ + --precision "${precision}" + + run_qibojit_reference "${nqubits}" "${circuit}" "${circuit_opts}" "${cfg_name}" + done + fi + + echo + done +done diff --git a/qibotn b/qibotn new file mode 160000 index 0000000..2f55e20 --- /dev/null +++ b/qibotn @@ -0,0 +1 @@ +Subproject commit 2f55e20b9ab36e1415158cc6ac6ea5e3e856a5fe diff --git a/workload-qaoa/dense_vector_mps_mpi_qaoa.json b/workload-qaoa/dense_vector_mps_mpi_qaoa.json new file mode 100644 index 0000000..636efe2 --- /dev/null +++ b/workload-qaoa/dense_vector_mps_mpi_qaoa.json @@ -0,0 +1 @@ +{"MPI_enabled": false, "expectation_enabled": true, "use_mps": true, "max_bond_dimension": 20, "svd_cutoff": 1e-10} diff --git a/workload-qaoa/problem2.sh b/workload-qaoa/problem2.sh new file mode 100755 index 0000000..abf58c8 --- /dev/null +++ b/workload-qaoa/problem2.sh @@ -0,0 +1,4 @@ +#!/usr/bin/bash + + +CUDA_VISIBLE_DEVICES="" mpirun -np 1024 python ../qibojit-benchmarks/compare.py --circuit qaoa --nqubits 11 2058 --filename "qaoa.dat" --library-options backend=qibotn,platform=quimb,computation_settings="dense_vector_mps_mpi_qaoa.json" --nreps 1 --precision 'complex128' > run.log 2>&1 diff --git a/workload-qft/dense_vector_mps_mpi_qft.json b/workload-qft/dense_vector_mps_mpi_qft.json new file mode 100644 index 0000000..9ddb6b0 --- /dev/null +++ b/workload-qft/dense_vector_mps_mpi_qft.json @@ -0,0 +1 @@ +{"MPI_enabled": false, "expectation_enabled": true, "pauli_pattern_auto": true, "pauli_pattern_style": "mixed", "use_mps": true, "max_bond_dimension": 20, "svd_cutoff": 1e-10} diff --git a/workload-qft/problem1.sh b/workload-qft/problem1.sh new file mode 100755 index 0000000..4b1cdc9 --- /dev/null +++ b/workload-qft/problem1.sh @@ -0,0 +1,4 @@ +#!/usr/bin/bash + + +CUDA_VISIBLE_DEVICES="" mpirun -np 512 python ../qibojit-benchmarks/compare.py --circuit qft --nqubits 1 512 --filename "qft.dat" --library-options backend=qibotn,platform=quimb,computation_settings="dense_vector_mps_mpi_qft.json" --nreps 1 --precision 'complex128' > run.log 2>&1 diff --git a/workload-qft/qft.dat b/workload-qft/qft.dat new file mode 100644 index 0000000..3287c16 --- /dev/null +++ b/workload-qft/qft.dat @@ -0,0 +1 @@ +[{"datetime": "2026-05-19 16:50:16", "nqubits_min": 1, "nqubits_max": 512, "nreps": 1, "secure_log": ["Zxn1fH3tipknIzEEkRVdz9bO1Pwan1c29+Y3ChrWpNesTGlr+UBIJOT2IImAunuqp0M4MYRORDP4ixzClsze/iI9y8ITjEOSEXIXjP4eSsvfypsHVA6eU3vr0vgmiB381AHGoZG/+Zpi4eD6tF3CRsqmGQow33kCcctLjpyH6azT4ZU9etQ260Y9ZBS8teRLAm7qKA4bev/0tgR+qUXOLiylshrBuKWbmGlr0/Tqc0DSi0Ejt5b3P7dNMX99pcjzEZ7+62g8Tuk7qEy28VShpheR7unG4/LsNb1WnCnN4EjdAFWh01ckNx0laYnWeqc17lE4oTMjoG0r6jXkGWMWJA==", "dRW07bPSJH0HsBYdw1Gd9I/z4rc3/Nh/rgNc5JkV9lDxYacI0BzKWQRNswVg2T0HfP9pxdbR3cNFYKuAEhHoGoi3KRXA72sv4/YsIJUzOhQYd6le5wCACxM+5ckMkxNVIgw23hri7PR2i6WtO0N/9Vxs9BlnwSA1Uusykf65nEHCWuNWkxsN/wRbmeBVUIgb4rYneYwTU4Zzh3+UTLw0Nx2MjvnENhJnh/eykpQU2G0bm9apeKuwyuCQmC0IpJ6TAPPRt/ocvwYrF5nMzV3p2vgE7ZozZsh9jJe4CVKY9oCGqaLw9Ch92rdc4jRX25w9Eq7mmvIt4wd78OyYzME1og==", "U4ggIezP0KjCZwubkn48jcFdU8/wyNcY0sz6+TlqjDLeul4PqfLiOt+fzLj32rrM14pw9UmDLB1KDpPzPRoM4Gid+A1E59nb8k5Ww+OI+ctNzmDB/lfmSmiY3k+xmgj60BHHPXxsM0wh//+TqXJIMf7xAB6lKibXFYWElWbJAE1lfcyb6rwq8anoz8VQogT2r5lQXDW68+VeTMmbXaDrGROWmc0+4M0t1BS1ZNcQ1/+eF+/sJ6dB5N352hGvSxKVsCSizuF6TgYxme0Tf1VZ3HiivDKphje/X4pFbsYA9BEfHy2MoPaZeGntisaA6QfivGXpxrubsPBcGHCQB4ucag==", "W2JZygFmeiPaQ0m0H7wCgS0hMakGsGFmc/ymGP9IHDBZEpRzDpS34tcNVnh/uiIe991nBnD3+BKPEhLghFrJTogqJ1AuSbKYkNfcZdUd1E/CoPfOGC5jppmm0kH6BwAf1ApNpXldZzDwQ76QOWJSbmaqeeHvPk5v06YIVj0O6WHv1hjvmobJfwUtPCBYV5No8Qu447n3VWmwXKody4ggKm+60ZwleSA38GtmZ8ZjjitpZrsnLc6LBSJKCddMibFE944WGcFJLJ/LgNp4Al9TWo05k5ZToeE+1qg2zmr3MuvNH3be9czco+ipai54SYY8cKvL7xZwIsbol+sQ4qcTmg==", "hnH9Kx9N76hfJMIzOrYePFqpFj5Skjo48nhslbRRXXT4PHlarsvEIPRZVlcbFYGRGvrk6kcsw/qle6fRxVdpq3KpB+o9ZG5W7AUOdHRW4qgqpFOo7rZmbKhYRRXFNfcWlxqpbSzujzo+qY/GjOqw/ZebEzskk1zSIviLDbp7mYmJ2gPxgbjdUmJpl611Uk1jmmcmZV8AGut6zZTMoC1vEirFIEDbDIDBciF6SNzA5VZD58gdqGj0HqxR5hGDQOFxGekKSyJqIzg2/E22+K47uPVNowYIIjE5tNZXoxx6jQ/NBfOi8FIa4VrCCnrlqRs54v4iCpJeoQr9jh5mD5uqRg==", "D3Y90zGx7pLiI0rB5hRtgCeL7BWPYGuHw22NYCEVEncpL1W8hXlEhF/V9NfrYb/j5o2kHOBEL8tAmDbkQU37HJl91CdZUZSMfX9/5HsL/dVIHHo0+UXX2lUeFK7FallzdKCy0H/hIJyY0N2VXq1wBjRMM4/DbOR7oMaDs2emQ2DhKVZccn6Du6Tc1J8eHxyS02EzXxbM6zH82h0+worB09br+kWr0EWeAuYIFN6CY1vSNv/u4NRA4BrjbQFTzNhrDrFl8dqVAojGGIVwoxlCywuIPNAecSfyfO2OJdPqlCG9YGhE5QTfYpmrD7i6U+MeLneWKQa154r62VbH5YvH1Q==", "oDZpDLyEgun+C8SB9ML22sbLKLXJ76oQaem5VONLxvPAE9U/Vhdp4+ZbbATBhNXi+kvc2eymdivhe2EY2PIaFSfi/NZKwCVP3ERlHtegBMxFR2seK5//9zUF5/lAvwk9IplpfYDsM7EwoYqsZx5sWKRaZX0Wfyt9PSIbhI6g3eOU3cHBtnfnwaCzVNL1ZdatuEw438NIooLxiekwTNQXE9t2e+OTX4qpnJbC42XXrMqNV7fgwX3IkGwJGZevynR1SnWmsZWtq71iWa7YM4AUsBqT68AE5DLjRWS5pWfyZ4B1UQc9sjZ9bLQ2YIZd4Be7HOJ8B5wBl7gsh852BHMfCg==", "aXMJu8meviuR+EqfR8oL5Td/djEPDJwi2x00k31ewhdyu3zfIA4tXL/2k191kCJqopNzw0M6oCbbF0TKa9qLQ1XmENy2ZTxyOwQfuQUy4M2tBH9Mq5UWgO4IdvF5VgNVvdQslZsNZWpgblN/KW0gJgb7/pV+Q+9ooa77Ko6ayLMThRvNdn7ro95WKkUoHuR3kCfa5FgbnBOZyDB3lLP3wzjOOO3kUikC1TsCdpyJFdk+RDGGY/OS1EsAxVf5ZLu0uYndFmgnNfR/52hLVqwh+3oPak7GCnE57v5qwcPeK1bBd/rcDfWh+19d7J3l/FqW/CorFNpfuIJSkczMH44PdA==", "FrcAnNCms5SWt/YTGIATvzf4lq89XUAGE8OFYCpgljV0UrBX/noDv8cTHPsBzo4tvw6cy1kfkDsAB8MHwqIVrVxwB/amRsW/R45uYxWXfSaSWUwgnZfa61rBinS2THWh59Od0i8iBmdWYD5AZMwM9v7jwqjwOQEZ541VYrj3QzZ9dt6dL6gAjDt7aeHW4+VrAovMVhcILuyzfTS83mv3FIq3nqHCAekGF9wOAY6VLOzW5xfkbPFunbXcZly8sT+UOAeV37/i+L1S0jn7QWCa+eX1qHCzTuc4jsTPgWd1ATtiiPzlP5NDz7ZxrK/i+VJ3VvsWYAUc9j6zW1lb9X1Wdw==", "Gk2Ml+WS2Tbvj+ggpA/Ud3Lv0PkSh2+Efmo0xWulmaP6m1pL9IQwVq7x3WJ5TOU4+XpWyFH4gOkHelmBLlZ+HFAPIk3HfOU9FlUZHSXkWb2LrcNdSZHhsUoGTNrhEEjlBjY1WeAC7qzWy/I9w/f/WOh6paXsoYWvOKsK++08l2RExIeVvo8TRmUG0KjMrgwG0cHVYk4bE/Z2q2W9SRtvkIgE+1lbfVRGrzD6K58Ptzu1j6bsvColijgcTNHUTF4H2EhnqC0UC1Hys1sqziqFWviSCNU9fJiM2Deb2Vk+H4otuYKoNG2BcS90PwpKJt51wCO9h4CRvu0k9AQ40jLzLw==", "ggylVEANusOFPCac7aTbwlcWDWlHBmqopK1lVgrZv09bzMDVVQAEIa1RsC3ONiMdUsU5ty5Q5NmBOwdmK/8LeOI6Ghn/X11oxm2VeG6AmLYQ2/rj0G045d7NlBBAL2jHDk2mx32YbXcU6Kh6xsEoAKjoB04b9RaTEO7KEVsyVIPIbwJ/svrSCGgo1RlNA98mGbJjrpWFfUCU5d4cSI+0VcOJAsTbpq7cXNPel2f4JLWjlyuUzMgS68Vmt2McqTbTO3Z+n29WmD/qxi3GlISbi3PHLU4DL0U+vzjxeUsP9CJrSIWp44Dp7Ui9sM7/oK0gLeiaNsueobTvMKnyXpvw6g==", "IAQc4JD5p60D2zrm4UCG/T/ULt2DQ8e1sOWYgFuXJc2ORABMv+jBB4yXh3iByVSfscNNZuHSSvyQ8qmO1QPHAWylB8EB73IDFCmzGRFabzVbH5usKUWQYKwpIblBTjr3X9OY+wDtBvTFs46nJipC4nq2zPl0fo5RLnWKGPoE9YYF6uwuwnuH9xgS0F5RfqwHS4SIEIIg04AS+waMTLFktrad84woSTcy/Uz5gVTHDWInKKGI9SP4lyLl7MWyTUZ6ygOHppdT5oYMOfM08h7GWT7tblPD47P4rf2mOpNXfqW3ZTu8aaL7f2fLMH7Nvt3fJUiOYcK4VWixwRo+7cf3cg==", "FvDWD+Zcmxk93Ex6ppQiSS4OA1Q2sDlI6vq6/eDYFNpZr2YWn+NA0ZgA3eKZ/w/HfkuSfJMSBIIJkmpSS4+Sk3/H/+kggVII6AufXptKyArpk9aA10DXX8Bt5ntwLdVPPamjT+tixvZdoKGmkG3OC12XURPgVaP59JPhirDmp62hg2rU1huFr9c4D+Oo8w6toHZgllk5YavJR2B1GCYSlh09rUTEZLrGV/B6F01NhOZwxhCAq5dVikMZvECdT8+UwdMcEaADvb0HEFZlKsKbR0iqKCFGulGvWN0iPDb1ec1fSWHf4hdz3DQqJUOkioOgxpF6QeF9qJRpyMB3FM0yag==", "Edf2xvHXvjSZJXtTcwHfdpFNvluy4Y+Pp4fX6ltl3ZC8B2w6Bo4P00chGLF346pQCYFKudd5z90ZHKnwz71/x/43UxdjbF6gMSOGA76ttCOaiPD195a0U8JGAKxqzVXCqlZu18h2Iuxjy4H1f8MonnPMLdjH+VweIHKQKFwBa2GZKzJ2DJXcDRGfbT7PXKUOe2iZ1q49piOprnwV1AWWbTIRvYNLa2P3JGbq3iLeT48GtOWEBctNpXtR2FCJfg3tADuFyx+SIRC4GvQk15pd/vKxiB/wGEwOsC5ktl8WDYRU+DrEAbJh4FuRiuzEW6JY7aBztkW+tpwckpiBtobYEA==", "QWTIJBZSV9LFFpsl6lIutxVsN6gy7eNHjxiZ6hbyHMXIwWT2X+spBzeQ+yWmYc67WCYSqaGWmFQYhFDlot9NMGwYmwi2CxrFfJMC7MmHdOYpIzBrEzVmWhVxWRItGubAbiuhmwD4DlJVRAGMIBnQgPCVzhPGh3bZ8LWhbd88IhKt8r20iqzVDfXe++UmOQGdNVupUjVHKnYeO2JUs7fD6yNiZ5hA6wRpDZfk6nPxp3nl9Cqn8Ml+KiboJvUd0KEJRvwQXCjMrChr1lxO4Q1oTNIU9QQj9N37HK6f9o89cJR0EDaRqeyPEcqWQnokFL0Ml01wc0USOw8ZyrHBveg/FQ==", "A2yX6wXDwTZaK9y5L3fdhUhlhJmKhcEsO30z+W+r3WTfpwFihQTXSBELEIfBldcI2fiuZrsaTp5d54OFBvBANZhSDnWfRbCaH4XyeAwiIRVDtnetqT1o8i+8FHZMUTucZrPVPJ1CYw6c5WQO8UaklfWonabc50TC/GgIV8tFwTFPDc4X5323Ao63FQWSmo2OfVNvFI+lN1hdF9M4XB1uRKc0Ga7G6QfMfXTWSBrPx1P89MmcBZY2pIPhv301EUqbQKeQzWVXQvbaE2osUz1TGHg+SFVKYeaZdwmJFpHOogkcmIpz1de+1itiqc5yAVIgpRErzER926Q+A+LWPxtPgg==", "IZOuAk1AJu8JqdDCsx4duKciYnHVrCS4lDhA+E/iJKRI+7m4KAJ825V9O9wYlWe3Bm7QKmPjj8Cg08+96NP9OxPv+bZyH60eVpT2x+kOlS2oBmdYFvVsvlq041ObBgGiYqnu9JDeiO6yvYae8w6wxWhZiEvPwKEShb6qwMonU/8QSwIBIv2xHQvK6mqj6vUUH+5okcw8y0czFMrXpf6C8I0Tyj1sC72L+vgmEMcxoSfZ2rJMhnVWVGsj/rl1EHbBQbYWDASnobsKUPIa0xGY9aPPv20K7T7ll032+nu9E81c11K+sPsFMHEUU/caLkvUc2EZEmx/9kyqrBBdcvGsUA==", "kyBv/OkGPPH6357yWLFcJ1FNI5ZGFUcEi/ufHemW6gdWRFAU7ULfFujaKdFuntxfE9r30wC3/0SCTkz29zNYMK5zc1cISFQqH1jF0QxBMys8lGFJSoDrDn+SBhJEWGmP2XpRbBc1+p18grPkers5vozqevO/UKIkzJSy7PNB4i8hMXSB73AaaxlVNy21pN/DKwxNhBLPP2S1yZ8RAxRVeaJT/H078XDVgyCk7jbEQ+PVBsIbaAW9ovHZCyuTBppm12aKq+6FooQHU1A1lyiwjCfcXroizDKlJkKNgoR9UYK5gIrTjNHCkSAcB69gWBygIzNWkLODoesvWkc+B1WzXw==", "ENm/id8Riv0petS3CCCDZ7RJOXQDD5N8mJDnzWbHYEltALy3o02EJ0eTRsSj2Ga2ya3ORUOJY+NS4YVYlzvBuG6YK3kJydWj7zT0VM9Gi8tYZ3I+TGXW3LZhGui1g7qCRzgFtZzl3yCeL6DQIw+f+JiTmwKyl2SLVDcXh6Gx+5vlAMB0tPGXaBY1EuFfoy0s0FAkc9Us4dhKSmqPMpgF5lXmOMyP+qCmL3uPjRmCCC5spdvTfo4TuMGTjvLSBO+ORC23/xuMxXp1OGCOK8hk/M9O+LhaB4Y12xjRYc+xso68QAv1TpbUN5Brq/QLNwQLwLD9sqV5kQ7dqzLDnUve5A==", "Y48K5b+t9QAE8fDJnllLQK22El4vW3CEqDhqIz0nGPW5jaVILct/VwYznN1VfPrXNY6rEyE3vm7kM0u4itTG2lkKHlrIZRH3ubaD89MLMr2JaAyM3QvgQ/a/2Ymmz81lQHj+hsT2tHGqvCNEMwZhpxwUV334o3b3SrypfI8xq8hFquEsrAlffYWQRKivSvjMk08qawmlpScgj+U/2U+rNgVRm67fkF83lws+T5SGWpHoMEvtW3eO1lv2Um0qscczf0P6kgMQXnAqkFN5IEbbxFVW7H7mpcqC8kYQo2rHvHr5ZNAv53WyWgqJmUe/1uXCr+YYBQeQJXk2socb8lP2UQ==", "LlH5TsAxtv+17cWw/py6J01Ab12015yZRKIdvO0x9um6zyz8/jHjTq0SCiOku5pj8elS1RCd8XnxAA1SCXQ/PZT/ajwp+nrhPlvp7i4mTSkzMq6MYgJ7a9zJApOFdUV9vCdseYt3F/GielCIribr4e0SJC/8/rP+7O02ii/trcOl8wxRM/0T7MFYW/ZcyMJEyAx8YQmmZotXkzg2Vq+Db1w+pAdCD3KdxsbyH/fkrFWm8fLdA/EB5MRQ9evkjDPr6HG+lJnG07o9dNJfyaxPL9qpZnZ5+qEBSRi9iYm3Ft2kdSy2DZuWm0xREEkuZppJvwzv8AMq2jPV09GykP7kNA=="], "import_time": 8.023608684539795, "library_options": "backend=qibotn,platform=quimb,computation_settings=dense_vector_mps_mpi_qft.json", "library": "qibo", "precision": "complex128", "device": "/CPU:0", "version": "0.3.2", "circuit": "qft", "circuit_options": ""}] \ No newline at end of file diff --git a/workload-supremacy/dense_vector_mps_mpi_supremacy.json b/workload-supremacy/dense_vector_mps_mpi_supremacy.json new file mode 100644 index 0000000..9ddb6b0 --- /dev/null +++ b/workload-supremacy/dense_vector_mps_mpi_supremacy.json @@ -0,0 +1 @@ +{"MPI_enabled": false, "expectation_enabled": true, "pauli_pattern_auto": true, "pauli_pattern_style": "mixed", "use_mps": true, "max_bond_dimension": 20, "svd_cutoff": 1e-10} diff --git a/workload-supremacy/problem3.sh b/workload-supremacy/problem3.sh new file mode 100755 index 0000000..1f091a8 --- /dev/null +++ b/workload-supremacy/problem3.sh @@ -0,0 +1,4 @@ +#!/usr/bin/bash + + +CUDA_VISIBLE_DEVICES="" mpirun -np 1024 python ../qibojit-benchmarks/compare.py --circuit supremacy --circuit-options 'depth=80' --nqubits 1477 2500 --filename "supremacy.dat" --library-options backend=qibotn,platform=quimb,computation_settings="dense_vector_mps_mpi_supremacy.json" --nreps 1 --precision 'complex128' > run.log 2>&1