# qiskit_alt

## qiskit_alt

This Python package uses a backend written in Julia to implement high performance features for
standard Qiskit. This package is a proof of concept with little high-level code.

Installing and managing Julia and its packages is automated. So you don't need to learn anything about Julia
to get started.

The highlights thus far are in [benchmark code](./bench/), which is
presented in the demonstration notebooks.
There is one [demonstration notebook using `pyjulia`](./demos/qiskit_alt_demo.ipynb)
and another [demonstration notebook using `juliacall`](./demos/qiskit_alt_demo_jc.ipynb).

The main application-level demonstration is computing a qubit Hamiltonian as a `qiskit.quantum_info.SparsePauliOp`
from a Python list specifiying the molecule geometry in the same format as that used by `qiskit_nature`.

* The Jordan-Wigner transform in qiskit_alt is 30 or so times faster than in qiskit-nature.
* Computing a Fermionic Hamiltonian from pyscf integrals is several times faster, with the factor increasing
  with the problem size.
* Converting an operator from the computational basis, as a numpy matrix, to the Pauli basis, as a `qiskit.quantum_info.SparsePauliOp`,
  is many times faster with the factor increasing rapidly in the number of qubits.

You might want to skip to [installation instructions](#installation-and-configuration-notes)

### Table of contents

* [Motivations](./Motivations.md)

* [Demonstration](#demonstration)
    * [Zapata demo of Jordan-Wigner transformation in Julia](https://www.youtube.com/watch?v=-6VfSgPXe4s&list=PLP8iPy9hna6Tl2UHTrm4jnIYrLkIcAROR); The
      same thing as the main demonstration in qiskit_alt.

* [Installation and configuration notes](#installation-and-configuration-notes)

    * [Compilation](#compilation) Compiling a system image to eliminate compilation at run time.

    * [Using qiskit_alt](#using-qiskit_alt) First steps.

    * [Manual Steps](#manual-steps) Details of automatic installation.

* [Julia Packages](#julia-packages) Julia packages that qiskit_alt depends on.

* [Troubleshooting](#troubleshooting)

* [Communication between Python and Julia](#communication-between-python-and-julia) Options for the language interface.

* [Development](./Development.md). Instructions for developing qiskit_alt.


## Demonstration

* There are a few demos in this [demonstration benchmark notebook](./demos/qiskit_alt_demo.ipynb)

* The [benchmark code](./bench/) is a good place to get an idea of what qiskit_alt can do.

* Here are [some demonstration notebooks](https://github.com/Qiskit-Extensions/QuantumOpsDemos) of the Julia packages behind `qiskit_alt`.

* [Zapata demo of Jordan-Wigner transformation in Julia](https://www.youtube.com/watch?v=-6VfSgPXe4s&list=PLP8iPy9hna6Tl2UHTrm4jnIYrLkIcAROR); The
  same thing as the main demonstration in qiskit_alt. This is from JuliaCon 2020.

## Installation and Configuration Notes

### Basic

* `qiskit_alt` is available on pypi: `pip install qiskit_alt`

* Complete installation by running `import qiskit_alt` followed by `qiskit_alt.project.ensure_init()`. See [`julia_project`](https://github.com/jlapeyre/julia_project)
  for more information.

    * If no Julia executable is found, `jill.py` will be used to download and install it. It is *not* necessary
      to add the installation path or symlink path to your search PATH to use julia from qiskit_alt.
      Before offering to install Julia, `qiskit_alt` will search for julia as [described here](./Install_Julia.md).

    * The Julia packages are installed the first time you run `qiskit_alt.project.ensure_init()` from Python. If this fails,
      see the log file qiskit_alt.log and the [manual steps](#manual-steps) below.

* Check that the installation is not completely broken by running benchmark scripts, with the string "alt" in the name:
```sh
python ./bench/run_all_bench.py
```
Note that the folder `bench` is not included when you install via `pip install qiskit_alt`.
But it can be [downloaded here](./bench/).


### More installation details

* `qiskit_alt` depends on the following three packages. It is probably not necessary to read about them to install `qiskit_alt`, but might help.


    * [pyjulia](https://pyjulia.readthedocs.io/en/latest/index.html) is used to communicate with Julia.
      The [installation notes](https://pyjulia.readthedocs.io/en/latest/installation.html) may be useful.

    * [juliacall](https://github.com/cjdoris/PythonCall.jl).

    * [`julia_project`](https://github.com/jlapeyre/julia_project) for managing Julia dependencies.


*  `pyjulia` and `juliacall` are two packages for communication between Python and Julia. You only need
   to import one of them. But, you won't import them directly.

*  When you initialize with `qiskit_alt.project.ensure_init()` the default communication package is chosen.
   You can also choose explicitly with `qiskit_alt.project.ensure_init(calljula="pyjulia")`
   or `qiskit_alt.project.ensure_init(calljula="juliacall")`

* The installation is interactive. How to do a non-interactive installation with environment variables is
  described below.

* You may allow `qiskit_alt` to download and install Julia for you, using [`jill.py`](https://github.com/johnnychen94/jill.py).
  Otherwise you can follow instructions for [installing Julia manually](./Install_Julia.md).

* We recommend using a virtual Python environment with `venv` or `conda`. For example `python -m venv ./env`,
  which creates a virtual environment for python packages needed to run `qiskit_alt`.
  You can use whatever name you like in place of the directory `./env`.

* Activate the environment using the file required for your shell. For example
  `source ./env/bin/activate` for `venv` and bash.

* Install `qiskit_alt` with `pip install qiskit_alt`.

* Install whatever other packages you want. For example `pip install ipython`.

* Configuring installation with environment variables. If you set these environment variables, you will not be prompted
  during installation.
    * Set `QISKIT_ALT_JULIA_PATH` to the path to a Julia executable (in a Julia installation). This variable takes
      precedence over other methods of specifying the path to the executable.
    * Set `QISKIT_ALT_INSTALL_JULIA` to `y` or `n` to confirm or disallow installing julia via `jill.py`.
    * Set `QISKIT_ALT_COMPILE` to `y` or `n`  to confirm or disallow compiling a system image after installing Julia packages
    * Set `QISKIT_ALT_DEPOT` to `y` or `n` to force using or not using a Julia "depot" specific to this project.

* `qiskit_alt.project.update()` will delete `Manifest.toml` files; upgrade packages; rebuild the manifest; delete compiled system images.
  If you call `update()` while running a compiled system image, you should exit Python and start again before compiling


### Compilation

*  To speed up loading and reduce delays due to just-in-time compilation, you can compile a system image for `qiskit_alt` as follows.
```python
[1]: import qiskit_alt

In [2]: qiskit_alt.project.ensure_init()

In [3]: qiskit_alt.project.compile()
```
Compilation takes about four minutes. The new Julia system image will be found  the next time you import `qiskit_alt`.

* `import qiskit_alt` takes about 8.5s before compilation, and 1.4s after compilation.

* The code is "exercised" during compilation by running the test suites of some of the included packages. Code paths
that are exercised during compilation will suffer no delay in the future, just like statically compiled libraries.
More test suites and exercise scripts can be included in the compilation.
And more Julia code can be moved from `qiskit_alt` into compiled modules.

* "compilation" has different meanings in Julia. Code is always precompiled and cached in a `.ji` file.
What happens during precompilation is described [here](https://julialang.org/blog/2021/01/precompile_tutorial/).
But, this is not the kind of compilation we are considering here.

## Using qiskit_alt

This is a very brief introduction.

* The pyjulia interface is exposed via the `julia` module. The `juliacall` module is called `juliacall`.
However you should *not* do `import julia` or `import juliacall` before `import qiskit_alt`,
and `qiskit_alt.project.ensure_init()` (or `qiskit_alt.project.ensure_init(calljulia="pyjulia")` or
  `juliacall` with `qiskit_alt.project.ensure_init(calljulia="juliacall")`)
This is because `import julia` will circumvent the facilities described above for choosing the julia executable and the
compiled system image.


* Julia modules are loaded like this. Note that `qiskit_alt.project.julia` points to either `julia` or `juliacall` depending
on which was chosen.
```python
import qiskit_alt
qiskit_alt.project.ensure_init(calljulia=interface_choice)
Main = qiskit_alt.project.julia.Main
```

`import qiskit_alt`; `import julia`; `from julia import PkgName`.
After this, all functions and symbols in `PkgName` are available.
You can do, for example
```python
In [1]: import qiskit_alt

In [2]: qiskit_alt.project.ensure_init()

In [3]: julia = qiskit_alt.project.julia

In [4]: julia.Main.cosd(90)
Out[4]: 0.0

In [5]: QuantumOps = qiskit_alt.project.simple_import("QuantumOps")

In [6]: pauli_sum = QuantumOps.rand_op_sum(QuantumOps.Pauli, 3, 4); pauli_sum
Out[6]:
<PyCall.jlwrap 4x3 QuantumOps.PauliSum{Vector{Vector{QuantumOps.Paulis.Pauli}}, Vector{Complex{Int64}}}:
IIZ * (1 + 0im)
XYI * (1 + 0im)
YIX * (1 + 0im)
ZIZ * (1 + 0im)>
```

In the last example above, `PauliSum` is a Julia object. The `PauliSum` can be converted to
a Qiskit `SparsePauliOp` like this.
```python
In [7]: from qiskit_alt.pauli_operators import PauliSum_to_SparsePauliOp

In [8]: PauliSum_to_SparsePauliOp(pauli_sum)
Out[8]:
SparsePauliOp(['ZII', 'IYX', 'XIY', 'ZIZ'],
              coeffs=[1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])
```

### Managing Julia packages

* Available Julia modules are those in the standard library and those listed in [Project.toml](./Project.toml).
You can add more packages (and record them in `Project.toml`) by doing `qiskit_alt.project.julia.Pkg.add("PackageName")`.
You can also do the same by avoiding Python and using the julia cli.


## Julia Packages

* The Julia repos [`QuantumOps.jl`](https://github.com/Qiskit-Extensions/QuantumOps.jl) and
[`ElectronicStructure.jl`](https://github.com/Qiskit-Extensions/ElectronicStructure.jl) and
[`ElectronicStructurePySCF.jl`](https://github.com/Qiskit-Extensions/ElectronicStructurePySCF.jl) and
[`QiskitQuantumInfo.jl`](https://github.com/Qiskit-Extensions/QiskitQuantumInfo.jl)
are not registered in the General Registry, but rather in [`QuantumRegistry`](https://github.com/Qiskit-Extensions/QuantumRegistry) which contains just
a handful of packages for this project.

## Testing

The test folder is mostly out of date.
<!--
In addtion to the code in the `bench` directory, there are test directories with just a few tests
in them. They can be run for example via `pytest ./test`. The juliacall tests are in a separate
folder because they can't be run in the same process as pyjulia tests.
 -->

<!--  LocalWords:  qiskit backend qisit pyjulia pypi julia cd venv env txt repo
 -->
<!--  LocalWords:  precompile terra executables toml cli url QuantumRegistry jl
 -->
<!--  LocalWords:  jit toplevel sys PYCALL repl linux NUM pyscf repos PyCall ji
 -->
<!--  LocalWords:  QuantumOps ElectronicStructure QiskitQuantumInfo juliacall
 -->
<!--  LocalWords:  numpy QuantumRegister Explicity supertype GPUs inlining eval
 -->
<!--  LocalWords:  calc num dtype jill symlink symlinks juliaup MSWin prebuilt
 -->
<!--  LocalWords:  keyscan precompiled precompilation PkgName pauli jlwrap IIY
 -->
<!--  LocalWords:  PauliSum im IYZ ZXI ZYI SparsePauliOp YYI ZZY XYZ YZZ TODO
 -->
<!--  LocalWords:  PackageName Fermionic qubits plugable vis jlPauliList JAOT
 -->
<!--  LocalWords:  PauliList invalidations ok AOT numba Bezanson JuliaCon IIZ
 -->
<!--  LocalWords:  XYI YIX ZIZ ZII IYX XIY
 -->
