Issue 217 | Docs: getting started 3 level tutorials (#222)

* Issue 217 | Docs: getting started 3 level tutorials
This commit is contained in:
Iskandar Sitdikov 2023-03-21 19:15:42 -04:00 committed by GitHub
parent 6fd956f429
commit e85392ded7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 2235 additions and 1197 deletions

137
README.md
View File

@ -6,6 +6,11 @@
# Quantum serverless
Quantum Serverless is a user-friendly tool that enables you to easily run complex quantum computing tasks.
With this software, you can execute Qiskit programs as long running jobs and distribute them across multiple CPUs, GPUs, and QPUs.
This means you can take on more complex quantum-classical programs and run them with ease.
You don't have to worry about configuration or scaling up computational resources, as Quantum Serverless takes care of everything for you.
![diagram](./docs/images/qs_diagram.png)
### Table of Contents
@ -13,139 +18,31 @@
1. [Installation](INSTALL.md)
2. [Quickstart](#quickstart-guide)
3. [Beginners Guide](docs/beginners_guide.md)
4. Modules:
4. [Getting started](docs/getting_started/)
5. Modules:
1. [Client](./client)
2. [Infrastructure](./infrastructure)
5. [Tutorials](docs/tutorials/)
6. [Guides](docs/guides/)
7. [How to Give Feedback](#how-to-give-feedback)
8. [Contribution Guidelines](#contribution-guidelines)
9. [References and Acknowledgements](#references-and-acknowledgements)
10. [License](#license)
6. [Tutorials](docs/tutorials/)
7. [Guides](docs/guides/)
8. [How to Give Feedback](#how-to-give-feedback)
9. [Contribution Guidelines](#contribution-guidelines)
10. [References and Acknowledgements](#references-and-acknowledgements)
11. [License](#license)
----------------------------------------------------------------------------------------------------
### Quickstart
Steps
1. prepare infrastructure
2. write your program
3. run program
#### Prepare infrastructure
In the root folder of this project you can find `docker-compose.yml`
file, which is configured to run all necessary services for quickstart tutorials.
Run in a root folder
1. Prepare local infrastructure
```shell
docker-compose pull
docker-compose up
```
:memo: For more advanced ways to deploy the project you have the guide:
[Multi cloud deployment](https://qiskit-extensions.github.io/quantum-serverless/guides/08_multi_cloud_deployment.html).
#### Write your program
Create python file with necessary code. Let's call in `program.py`
```python
# program.py
from qiskit import QuantumCircuit
from qiskit.circuit.random import random_circuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives import Estimator
from quantum_serverless import QuantumServerless, run_qiskit_remote, get, put
from quantum_serverless.core.state import RedisStateHandler
# 1. let's annotate out function to convert it
# to function that can be executed remotely
# using `run_qiskit_remote` decorator
@run_qiskit_remote()
def my_function(circuit: QuantumCircuit, obs: SparsePauliOp):
return Estimator().run([circuit], [obs]).result().values
# 2. Next let's create out serverless object to control
# where our remote function will be executed
serverless = QuantumServerless()
# 2.1 (Optional) state handler to write/read results in/out of job
state_handler = RedisStateHandler("redis", 6379)
circuits = [random_circuit(2, 2) for _ in range(3)]
# 3. create serverless context
with serverless:
# 4. let's put some shared objects into remote storage that will be shared among all executions
obs_ref = put(SparsePauliOp(["ZZ"]))
# 4. run our function and get back reference to it
# as now our function it remote one
function_reference = my_function(circuits[0], obs_ref)
# 4.1 or we can run N of them in parallel (for all circuits)
function_references = [my_function(circ, obs_ref) for circ in circuits]
# 5. to get results back from reference
# we need to call `get` on function reference
single_result = get(function_reference)
parallel_result = get(function_references)
print("Single execution:", single_result)
print("N parallel executions:", parallel_result)
# 5.1 (Optional) write results to state.
state_handler.set("result", {
"status": "ok",
"single": single_result.tolist(),
"parallel_result": [entry.tolist() for entry in parallel_result]
})
```
#### Run program
Let's run our program now
```python
from quantum_serverless import QuantumServerless, Program
from quantum_serverless.core.state import RedisStateHandler
serverless = QuantumServerless({
"providers": [{
"name": "docker-compose",
"compute_resource": {
"name": "docker-compose",
"host": "localhost", # using our docker-compose infrastructure
}
}]
})
serverless.set_provider("docker-compose") # set provider as docker-compose
state_handler = RedisStateHandler("localhost", 6379)
# create out program
program = Program(
name="my_program",
entrypoint="program.py" # set entrypoint as our program.py file
)
job = serverless.run_program(program)
job.status()
# <JobStatus.SUCCEEDED: 'SUCCEEDED'>
job.logs()
# Single execution: [1.]
# N parallel executions: [array([1.]), array([0.]), array([-0.28650496])]
state_handler.get("result") # (Optional) get written data
# {'status': 'ok',
# 'single': [1.0],
# 'parallel_result': [[1.0], [0.0], [-0.28650496]]}
```
2. Open jupyter notebook in browser at [http://localhost:8888/](http://localhost:8888/). Password for notebook is `123`
3. Explore 3 getting started tutorials.
For more detailed examples and explanations refer to [Beginners Guide](docs/beginners_guide.md), [Getting started examples](docs/getting_started/), [Guides](docs/guides) and [Tutorials](docs/tutorials).
----------------------------------------------------------------------------------------------------

View File

@ -3,6 +3,8 @@ services:
jupyter:
container_name: qs-jupyter
image: qiskit/quantum-serverless-notebook:nightly-py39
volumes:
- ./docs/getting_started:/home/jovyan/
ports:
- 8888:8888
environment:
@ -73,7 +75,7 @@ services:
KEYCLOAK_ADMIN_USER: admin
KEYCLOAK_ADMIN_PASSWORD: 123
KEYCLOAK_HTTP_PORT: 8080
KEYCLOAK_EXTRA_ARGS: "--import-realm"
KEYCLOAK_EXTRA_ARGS: "-Dkeycloak.import=/opt/keycloak/data/import/realm-export.json"
ports:
- '8085:8080'
depends_on:
@ -85,19 +87,20 @@ services:
gateway:
container_name: gateway
image: docker.io/qiskit/quantum-serverless-gateway:nightly
command: gunicorn gateway.wsgi:application --bind 0.0.0.0:8000 --workers=3
command: python manage.py runserver 0.0.0.0:8000
ports:
- 8000:8000
environment:
- DEBUG=0
- RAY_HOST=http://ray-head:8265
- CLIENT_ID=gateway-client
- CLIENT_ID=rayclient
- DJANGO_SUPERUSER_USERNAME=admin
- DJANGO_SUPERUSER_PASSWORD=123
- DJANGO_SUPERUSER_EMAIL=admin@noemail.com
- SETTING_KEYCLOAK_URL=http://keycloak:8080/auth
- SETTING_KEYCLOAK_REALM=Test
- SETTINGS_KEYCLOAK_CLIENT_SECRET=AQ3sZ4eiF7NhOtfxeUEGo0YN7uQBoUnO
- SETTING_KEYCLOAK_URL=http://keycloak:8080/
- SETTING_KEYCLOAK_REALM=quantumserverless
- SETTINGS_KEYCLOAK_CLIENT_SECRET=supersecret
- SETTINGS_KEYCLOAK_CLIENT_NAME=rayclient
- SITE_HOST=http://gateway:8000
networks:
- safe-tier

View File

@ -0,0 +1,252 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "66030e20-b384-4dcf-9c5f-7664f7ad1693",
"metadata": {},
"source": [
"# Getting started - level 1\n",
"\n",
"Let's write `Hello World` program using quantum serverless. \n",
"\n",
"We will start with writing code for our program and saving it to [./source_files/gs_level_1.py](./source_files/gs_level_1.py) file. Our program will be a Qiskit hello world example, which prepares a Bell state and then returns the measured probability distribution\n",
"\n",
"```python\n",
"# source_files/gs_level_1.py\n",
"\n",
"from qiskit import QuantumCircuit\n",
"from qiskit.primitives import Sampler\n",
"\n",
"circuit = QuantumCircuit(2)\n",
"circuit.h(0)\n",
"circuit.cx(0, 1)\n",
"circuit.measure_all()\n",
"circuit.draw()\n",
"\n",
"sampler = Sampler()\n",
"\n",
"quasi_dists = sampler.run(circuit).result().quasi_dists\n",
"\n",
"print(f\"Quasi distribution: {quasi_dists[0]}\")\n",
"```\n",
"\n",
"Next we need to run this program. For that we need to import necessary classes and configure them. \n",
"One of those classes is `QuantumServerless`, which is a client class to interact with compute resources.\n",
"It will help us run programs, monitor progress and fetch results.\n",
"\n",
"`QuantumServerless` accepts `Provider` as a constructor argument. Provider stores configuration where our compute resources are and how to connect to them. For this example we will be using provider which is connected to local docker-compose setup. For more information on docker-compose check out [docker docs](https://docs.docker.com/compose/), but for now you can think of it as your local environment manager. So, in this example programs will be running locally on your machine. If you want to run it elsewhere, you need to provide corresponding host and authentication details."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "81dd7807-7180-4b87-bbf9-832b7cf29d69",
"metadata": {},
"outputs": [],
"source": [
"from quantum_serverless import QuantumServerless, GatewayProvider"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "acdec789-4967-48ee-8f6c-8d2b0ff57e91",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<QuantumServerless | providers [gateway-provider]>"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"provider = GatewayProvider(\n",
" username=\"user\", # this username has already been defined in local docker setup and does not need to be changed\n",
" password=\"password123\", # this password has already been defined in local docker setup and does not need to be changed\n",
" host=\"http://gateway:8000\", # address of provider\n",
")\n",
"\n",
"serverless = QuantumServerless(provider)\n",
"serverless"
]
},
{
"cell_type": "markdown",
"id": "4dd85621-9ab0-4f34-9ab4-07ad773c5e00",
"metadata": {},
"source": [
"Now we need to run our program file, by creating an instance of `Program` and calling `run_program` method of our `QuantumServerless` client.\n",
"\n",
"`Program` accepts couple of required parameters:\n",
"- name - name of the program\n",
"- entrypoint - name of python file you want to execute\n",
"- working_dir - folder where your script is located. This is optional parameter and will be current folder by default. "
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "d51df836-3f22-467c-b637-5803145d5d8a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<Job | e4a801d2-f9eb-4392-b584-cbdd600755c8>"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from quantum_serverless import Program\n",
"\n",
"program = Program(\n",
" title=\"Getting started program level 1\", # you can choose any name you like. It is used to differentiate if you have a lot of programs in array.\n",
" entrypoint=\"gs_level_1.py\", # entrypoint is file that will start your calculation\n",
" working_dir=\"./source_files/\" # where you files are located. By default it is current directory.\n",
")\n",
"\n",
"job = serverless.run_program(program)\n",
"job"
]
},
{
"cell_type": "markdown",
"id": "39ee31d2-3553-4e19-bcb9-4cccd0df0e4c",
"metadata": {},
"source": [
"As result of `run_program` call we get `Job` which has `status` method to check status of program execution, `logs` to get logs of execution."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "cc7ccea6-bbae-4184-ba7f-67b6c20a0b0b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'SUCCEEDED'"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"job.status()"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "ca76abfa-2ff5-425b-a225-058d91348e8b",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'Quasi distribution: {0: 0.4999999999999999, 3: 0.4999999999999999}\\n'"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"job.logs()"
]
},
{
"cell_type": "markdown",
"id": "3b1113ef-e8ad-4ed9-b07b-9da2f2b9ea1c",
"metadata": {},
"source": [
"Also this object has `job_id` property that can be used if you want to access job results later.\n",
"To do so we need to call `get_job_by_id` method of `QuantumServerless` client."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "f942b76d-596c-4384-8f36-e5f73e72cefd",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'e4a801d2-f9eb-4392-b584-cbdd600755c8'"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"job.job_id"
]
},
{
"cell_type": "markdown",
"id": "a92069ba-0a3c-4c9f-8e8d-3916a2eb2093",
"metadata": {},
"source": [
"Users can fetch previously ran jobs from configured providers."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "45e2927f-655b-47a4-8003-f16e5ba0a1cd",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<Job | e4a801d2-f9eb-4392-b584-cbdd600755c8>"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"serverless.get_job_by_id(job.job_id)"
]
}
],
"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.9.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,436 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "3ff90cec-1b42-4744-9400-eb5a4bd1ef3a",
"metadata": {},
"source": [
"# Getting started - level 2\n",
"\n",
"In this tutorial we will explore a little bit more advanced example of a program that require some configuration, requirements setup, etc. \n",
"\n",
"Again we will start with writing code for our program and saving it to [./source_files/gs_level_2.py](./source_files/gs_level_2.py) file.\n",
"This time it will be VQE example from [Qiskit documentation](https://qiskit.org/documentation/nature/tutorials/07_leveraging_qiskit_runtime.html) and we also introduce dependency management and arguments to our programs.\n",
"\n",
"```python\n",
"# source_files/gs_level_2.py\n",
"\n",
"import argparse\n",
"\n",
"from qiskit_nature.units import DistanceUnit\n",
"from qiskit_nature.second_q.drivers import PySCFDriver\n",
"from qiskit_nature.second_q.mappers import QubitConverter\n",
"from qiskit_nature.second_q.mappers import ParityMapper\n",
"from qiskit_nature.second_q.properties import ParticleNumber\n",
"from qiskit_nature.second_q.transformers import ActiveSpaceTransformer\n",
"from qiskit.algorithms.minimum_eigensolvers import NumPyMinimumEigensolver\n",
"from qiskit_nature.second_q.algorithms.ground_state_solvers import GroundStateEigensolver\n",
"from qiskit.circuit.library import EfficientSU2\n",
"import numpy as np\n",
"from qiskit.utils import algorithm_globals\n",
"from qiskit.algorithms.optimizers import SPSA\n",
"from qiskit.algorithms.minimum_eigensolvers import VQE\n",
"from qiskit.primitives import Estimator\n",
"\n",
"\n",
"def run(bond_distance: float = 2.5):\n",
" driver = PySCFDriver(\n",
" atom=f\"Li 0 0 0; H 0 0 {bond_distance}\",\n",
" basis=\"sto3g\",\n",
" charge=0,\n",
" spin=0,\n",
" unit=DistanceUnit.ANGSTROM,\n",
" )\n",
" problem = driver.run()\n",
"\n",
" active_space_trafo = ActiveSpaceTransformer(\n",
" num_electrons=problem.num_particles, num_spatial_orbitals=3\n",
" )\n",
" problem = active_space_trafo.transform(problem)\n",
" qubit_converter = QubitConverter(ParityMapper(), two_qubit_reduction=True)\n",
"\n",
" ansatz = EfficientSU2(num_qubits=4, reps=1, entanglement=\"linear\", insert_barriers=True)\n",
"\n",
" np.random.seed(5)\n",
" algorithm_globals.random_seed = 5\n",
"\n",
"\n",
" optimizer = SPSA(maxiter=100)\n",
" initial_point = np.random.random(ansatz.num_parameters)\n",
"\n",
" estimator = Estimator()\n",
" local_vqe = VQE(\n",
" estimator,\n",
" ansatz,\n",
" optimizer,\n",
" initial_point=initial_point,\n",
" )\n",
"\n",
" local_vqe_groundstate_solver = GroundStateEigensolver(qubit_converter, local_vqe)\n",
" local_vqe_result = local_vqe_groundstate_solver.solve(problem)\n",
"\n",
" print(local_vqe_result)\n",
"\n",
"\n",
"if __name__ == \"__main__\":\n",
" parser = argparse.ArgumentParser()\n",
" parser.add_argument(\n",
" \"--bond_length\",\n",
" help=\"Bond length in Angstrom.\",\n",
" default=2.5,\n",
" type=float,\n",
" )\n",
" args = parser.parse_args()\n",
"\n",
" print(f\"Running for bond length {args.bond_length}.\")\n",
" run(args.bond_length)\n",
"\n",
"```\n",
"\n",
"As you can see here we used couple of additional things compared to `getting started level 1`. \n",
"\n",
"First, we are introducing dependency management by using the `qiskit-nature` module and `pyscf` extension.\n",
"We also using argument parsing to accept arguments to our program. In this case argument is `bond_length`. This means that we can, re-run our program over different bond lengths and produce a dissociation curve.\n",
"\n",
"\n",
"Next we need to run this program. For that we need to import necessary modules and configure `QuantumServerless` client. We are doing so by providing name and host for deployed infrastructure."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "79434a17-1222-4d04-a81a-8140ed630ed6",
"metadata": {},
"outputs": [],
"source": [
"from quantum_serverless import QuantumServerless, GatewayProvider"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "b6ec8969-8c3d-4b7f-8c4c-adc6dbb9c59f",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<QuantumServerless | providers [gateway-provider]>"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"provider = GatewayProvider(\n",
" username=\"user\", # this username has already been defined in local docker setup and does not need to be changed\n",
" password=\"password123\", # this password has already been defined in local docker setup and does not need to be changed\n",
" host=\"http://gateway:8000\", # address of provider\n",
")\n",
"\n",
"serverless = QuantumServerless(provider)\n",
"serverless"
]
},
{
"cell_type": "markdown",
"id": "544f7c64-ae1e-4480-b5d0-93f0c335eccd",
"metadata": {},
"source": [
"In addition to that we will provide additional `dependencies` and `arguments` to our `Program` construction.\n",
"- `dependencies` parameter will install provided libraries to run our script. Dependencies can be python libraries available on PyPi or any package source installable via pip package manager .\n",
"- `arguments` parameter is a dictionary with arguments that will be passed for script execution"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "3ee09b31-4c7f-4ff3-af8f-294e4256793e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<Job | 40754290-9768-4ba2-9689-6d72e9197878>"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from quantum_serverless import Program\n",
"\n",
"program = Program(\n",
" title=\"Getting started program level 2\",\n",
" entrypoint=\"gs_level_2.py\",\n",
" working_dir=\"./source_files\",\n",
" dependencies=[\"qiskit-nature\", \"qiskit-nature[pyscf]\"],\n",
" arguments={\n",
" \"bond_length\": 2.55\n",
" }\n",
")\n",
"\n",
"job = serverless.run_program(program)\n",
"job"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "420f2711-b8c6-4bf9-8651-c9d098348467",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'SUCCEEDED'"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"job.status()"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "7e3b0cc7-2f08-4b69-a266-bbbe4e9a6c59",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Running for bond length 2.55.\n",
"=== GROUND STATE ENERGY ===\n",
" \n",
"* Electronic ground state energy (Hartree): -8.211426461751\n",
" - computed part: -8.211426461751\n",
" - ActiveSpaceTransformer extracted energy part: 0.0\n",
"~ Nuclear repulsion energy (Hartree): 0.622561424612\n",
"> Total ground state energy (Hartree): -7.588865037139\n",
" \n",
"=== MEASURED OBSERVABLES ===\n",
" \n",
" 0: # Particles: 3.997 S: 0.436 S^2: 0.626 M: 0.001\n",
" \n",
"=== DIPOLE MOMENTS ===\n",
" \n",
"~ Nuclear dipole moment (a.u.): [0.0 0.0 4.81880162]\n",
" \n",
" 0: \n",
" * Electronic dipole moment (a.u.): [0.0 0.0 1.53218981]\n",
" - computed part: [0.0 0.0 1.53218981]\n",
" - ActiveSpaceTransformer extracted energy part: [0.0 0.0 0.0]\n",
" > Dipole moment (a.u.): [0.0 0.0 3.28661181] Total: 3.28661181\n",
" (debye): [0.0 0.0 8.35373319] Total: 8.35373319\n",
" \n",
"\n"
]
}
],
"source": [
"print(job.logs())"
]
},
{
"cell_type": "markdown",
"id": "94e3f04f-09df-4bc0-9715-643523207516",
"metadata": {},
"source": [
"---\n",
"If you want to run this program with different bond length you can run it 3 times. Programs are asynchronous, therefore each of instance of program will be running in parallel."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "5f4d4317-bcc9-4e1a-942a-a38ca5331261",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[<Job | ddb7331c-6db2-4e44-9449-b58ba53bd6b8>,\n",
" <Job | 52f77d9d-c669-4526-8b33-2fa9290b71dd>,\n",
" <Job | c31428f3-8c15-4257-a53d-fd899d40e29f>]"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"jobs = []\n",
"\n",
"for bond_length in [2.55, 3.0, 3.55]:\n",
" program = Program(\n",
" title=f\"Groundstate with bond length {bond_length}\",\n",
" entrypoint=\"gs_level_2.py\",\n",
" working_dir=\"./source_files\",\n",
" dependencies=[\"qiskit-nature\", \"qiskit-nature[pyscf]\"],\n",
" arguments={\n",
" \"bond_length\": bond_length\n",
" }\n",
" )\n",
" jobs.append(serverless.run_program(program))\n",
"\n",
"jobs"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "46fe3955-ac35-43d9-a5de-2a4e2cad1483",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"RUNNING\n",
"RUNNING\n",
"RUNNING\n"
]
}
],
"source": [
"for job in jobs:\n",
" print(job.status())"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "0988d6b4-03a4-4c87-ad1f-5b0526a7527e",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Running for bond length 2.55.\n",
"=== GROUND STATE ENERGY ===\n",
" \n",
"* Electronic ground state energy (Hartree): -8.211426461751\n",
" - computed part: -8.211426461751\n",
" - ActiveSpaceTransformer extracted energy part: 0.0\n",
"~ Nuclear repulsion energy (Hartree): 0.622561424612\n",
"> Total ground state energy (Hartree): -7.588865037139\n",
" \n",
"=== MEASURED OBSERVABLES ===\n",
" \n",
" 0: # Particles: 3.997 S: 0.436 S^2: 0.626 M: 0.001\n",
" \n",
"=== DIPOLE MOMENTS ===\n",
" \n",
"~ Nuclear dipole moment (a.u.): [0.0 0.0 4.81880162]\n",
" \n",
" 0: \n",
" * Electronic dipole moment (a.u.): [0.0 0.0 1.53218981]\n",
" - computed part: [0.0 0.0 1.53218981]\n",
" - ActiveSpaceTransformer extracted energy part: [0.0 0.0 0.0]\n",
" > Dipole moment (a.u.): [0.0 0.0 3.28661181] Total: 3.28661181\n",
" (debye): [0.0 0.0 8.35373319] Total: 8.35373319\n",
" \n",
"\n",
"Running for bond length 3.0.\n",
"=== GROUND STATE ENERGY ===\n",
" \n",
"* Electronic ground state energy (Hartree): -8.124024370249\n",
" - computed part: -8.124024370249\n",
" - ActiveSpaceTransformer extracted energy part: 0.0\n",
"~ Nuclear repulsion energy (Hartree): 0.52917721092\n",
"> Total ground state energy (Hartree): -7.594847159329\n",
" \n",
"=== MEASURED OBSERVABLES ===\n",
" \n",
" 0: # Particles: 3.998 S: 0.408 S^2: 0.575 M: 0.001\n",
" \n",
"=== DIPOLE MOMENTS ===\n",
" \n",
"~ Nuclear dipole moment (a.u.): [0.0 0.0 5.66917837]\n",
" \n",
" 0: \n",
" * Electronic dipole moment (a.u.): [0.0 0.0 2.91821215]\n",
" - computed part: [0.0 0.0 2.91821215]\n",
" - ActiveSpaceTransformer extracted energy part: [0.0 0.0 0.0]\n",
" > Dipole moment (a.u.): [0.0 0.0 2.75096622] Total: 2.75096622\n",
" (debye): [0.0 0.0 6.99225801] Total: 6.99225801\n",
" \n",
"\n",
"Running for bond length 3.55.\n",
"=== GROUND STATE ENERGY ===\n",
" \n",
"* Electronic ground state energy (Hartree): -8.049823374615\n",
" - computed part: -8.049823374615\n",
" - ActiveSpaceTransformer extracted energy part: 0.0\n",
"~ Nuclear repulsion energy (Hartree): 0.447192009228\n",
"> Total ground state energy (Hartree): -7.602631365386\n",
" \n",
"=== MEASURED OBSERVABLES ===\n",
" \n",
" 0: # Particles: 3.999 S: 0.378 S^2: 0.520 M: 0.001\n",
" \n",
"=== DIPOLE MOMENTS ===\n",
" \n",
"~ Nuclear dipole moment (a.u.): [0.0 0.0 6.70852774]\n",
" \n",
" 0: \n",
" * Electronic dipole moment (a.u.): [0.0 0.0 5.39179177]\n",
" - computed part: [0.0 0.0 5.39179177]\n",
" - ActiveSpaceTransformer extracted energy part: [0.0 0.0 0.0]\n",
" > Dipole moment (a.u.): [0.0 0.0 1.31673597] Total: 1.31673597\n",
" (debye): [0.0 0.0 3.34680868] Total: 3.34680868\n",
" \n",
"\n"
]
}
],
"source": [
"for job in jobs:\n",
" print(job.logs())"
]
},
{
"cell_type": "markdown",
"id": "95a1d859-a089-4d7f-ad88-b2acae1ed66d",
"metadata": {},
"source": [
"---\n",
"Other way would be refactoring program file itself to accept list of bond length and run them in a loop inside a program.\n",
"If you want 3 independent results, then running 3 programs would be a better fit. But if you want to do some postprocessing after execution of multiple function, then refactoring program file to run 3 function and postprocess them would be better choice. But at the end it all boils down to user preference."
]
}
],
"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.9.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,249 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "73944cc6-e22d-43ae-8716-78bb00360a0f",
"metadata": {},
"source": [
"# Getting started - level 3\n",
"\n",
"In this tutorial we will explore a little bit more advanced example of a program that require some configuration, requirements setup, etc. \n",
"\n",
"Again we will start with writing code for our program and saving it to [./source_files/gs_level_3.py](./source_files/gs_level_3.py) file.\n",
"This time, our program will run an estimator as a parallel function, computing the expectation value of a single observable over a set of random circuits. The results will be saved to a database, which means it will be stored in a formatted way and later on we can fetch results of or programs without looking at logs.\n",
"\n",
"```python\n",
"# source_files/gs_level_3.py\n",
"\n",
"from qiskit import QuantumCircuit\n",
"from qiskit.circuit.random import random_circuit\n",
"from qiskit.quantum_info import SparsePauliOp\n",
"from qiskit.primitives import Estimator\n",
"\n",
"from quantum_serverless import QuantumServerless, run_qiskit_remote, get, put, save_result\n",
"\n",
"# 1. let's annotate out function to convert it\n",
"# to function that can be executed remotely\n",
"# using `run_qiskit_remote` decorator\n",
"@run_qiskit_remote()\n",
"def my_function(circuit: QuantumCircuit, obs: SparsePauliOp):\n",
" \"\"\"Compute expectation value of an obs given a circuit\"\"\"\n",
" return Estimator().run([circuit], [obs]).result().values\n",
"\n",
"\n",
"# 2. Next let's create our serverless object that we will be using to create context\n",
"# which will allow us to run funcitons in parallel\n",
"serverless = QuantumServerless()\n",
"\n",
"circuits = [random_circuit(2, 2) for _ in range(3)]\n",
"\n",
"# 3. create serverless context which will allow us to run functions in parallel\n",
"with serverless.context():\n",
" # 4. The observable is the same for all expectation value calculations. So we can put that object into remote storage since it will be shared among all executions of my_function.\n",
" obs_ref = put(SparsePauliOp([\"ZZ\"]))\n",
"\n",
" # 5. we can run our function for a single input circuit \n",
" # and get back a reference to it as now our function is a remote one\n",
" function_reference = my_function(circuits[0], obs_ref)\n",
"\n",
" # 5.1 or we can run N of them in parallel (for all circuits)\n",
" # note: if we will be using real backends (QPUs) we should either use\n",
" # N separate backends to run them in parallel or\n",
" # one will be running after each other sequentially\n",
" function_references = [my_function(circ, obs_ref) for circ in circuits]\n",
"\n",
" # 6. to get results back from reference\n",
" # we need to call `get` on function reference\n",
" single_result = get(function_reference)\n",
" parallel_result = get(function_references)\n",
" print(\"Single execution:\", single_result)\n",
" print(\"N parallel executions:\", parallel_result)\n",
"\n",
" # 6.1 (Optional) write results to db.\n",
" save_result({\n",
" \"status\": \"ok\",\n",
" \"single\": single_result.tolist(),\n",
" \"parallel_result\": [entry.tolist() for entry in parallel_result]\n",
" })\n",
"\n",
"```\n",
"\n",
"As you can see we move to advanced section of using serverless. \n",
"\n",
"Here we are using `run_qiskit_remote` decorator to convert our function to asyncronous distributed one. \n",
"With that `my_function` is converted into asyncronous distributed function (as a result you will be getting function pointer), which means that the function no longer executes as part of your local python process, but executed on configured compute resources.\n",
"\n",
"Moreover, we are using `save_result` function in order to save results into database storage, so we can retrieve it later after program execution.\n",
"\n",
"Next we need to run this program. For that we need to import necessary modules and configure QuantumServerless client. We are doing so by providing name and host for deployed infrastructure."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "9130c64a-1e7f-4d08-afff-b2905b2d95ad",
"metadata": {},
"outputs": [],
"source": [
"from quantum_serverless import QuantumServerless, GatewayProvider"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "0f22daae-9f0e-4f7a-8a1f-5ade989d8be9",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<QuantumServerless | providers [gateway-provider]>"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"provider = GatewayProvider(\n",
" username=\"user\", # this username has already been defined in local docker setup and does not need to be changed\n",
" password=\"password123\", # this password has already been defined in local docker setup and does not need to be changed\n",
" host=\"http://gateway:8000\", # address of provider\n",
")\n",
"\n",
"serverless = QuantumServerless(provider)\n",
"serverless"
]
},
{
"cell_type": "markdown",
"id": "3321b4a0-b60d-433a-992a-79e5868d309b",
"metadata": {},
"source": [
"Run program"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "f556dd85-35da-48d1-9ae1-f04a386544d9",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<Job | 12>"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from quantum_serverless import Program \n",
"\n",
"program = Program(\n",
" title=\"Advanced program\",\n",
" entrypoint=\"gs_level_3.py\",\n",
" working_dir=\"./source_files/\"\n",
")\n",
"\n",
"job = serverless.run_program(program)\n",
"job"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "2de3fd64-9010-48d9-ac7c-f46a7b36ba81",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'SUCCEEDED'"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"job.status()"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "d6586e7a-388b-42cc-a860-abd4f6d514b9",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Single execution: [1.]\n",
"N parallel executions: [array([1.]), array([0.97400357]), array([1.])]\n",
"\n"
]
}
],
"source": [
"print(job.logs())"
]
},
{
"cell_type": "markdown",
"id": "29336f0b-ffcf-4cdb-931c-11faf09f15ff",
"metadata": {},
"source": [
"With `job.result()` we can get saved results inside of our function back. `.result()` call will return you whatever you passed in `save_result` inside the program file, while `.logs()` will return everything that was logged by job (stdio, e.g prints)."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "1fb8931f-c8e2-49dd-923f-16fa3a7a5feb",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'{\"status\": \"ok\", \"single\": [1.0], \"parallel_result\": [[1.0], [0.9740035726118753], [1.0]]}'"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"job.result()"
]
}
],
"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.7.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@ -0,0 +1,4 @@
.. nbgallery::
:glob:
*

View File

@ -0,0 +1,14 @@
from qiskit import QuantumCircuit
from qiskit.primitives import Sampler
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
circuit.draw()
sampler = Sampler()
quasi_dists = sampler.run(circuit).result().quasi_dists
print(f"Quasi distribution: {quasi_dists[0]}")

View File

@ -0,0 +1,69 @@
import argparse
from qiskit_nature.units import DistanceUnit
from qiskit_nature.second_q.drivers import PySCFDriver
from qiskit_nature.second_q.mappers import QubitConverter
from qiskit_nature.second_q.mappers import ParityMapper
from qiskit_nature.second_q.properties import ParticleNumber
from qiskit_nature.second_q.transformers import ActiveSpaceTransformer
from qiskit.algorithms.minimum_eigensolvers import NumPyMinimumEigensolver
from qiskit_nature.second_q.algorithms.ground_state_solvers import GroundStateEigensolver
from qiskit.circuit.library import EfficientSU2
import numpy as np
from qiskit.utils import algorithm_globals
from qiskit.algorithms.optimizers import SPSA
from qiskit.algorithms.minimum_eigensolvers import VQE
from qiskit.primitives import Estimator
def run(bond_distance: float = 2.5):
driver = PySCFDriver(
atom=f"Li 0 0 0; H 0 0 {bond_distance}",
basis="sto3g",
charge=0,
spin=0,
unit=DistanceUnit.ANGSTROM,
)
problem = driver.run()
active_space_trafo = ActiveSpaceTransformer(
num_electrons=problem.num_particles, num_spatial_orbitals=3
)
problem = active_space_trafo.transform(problem)
qubit_converter = QubitConverter(ParityMapper(), two_qubit_reduction=True)
ansatz = EfficientSU2(num_qubits=4, reps=1, entanglement="linear", insert_barriers=True)
np.random.seed(5)
algorithm_globals.random_seed = 5
optimizer = SPSA(maxiter=100)
initial_point = np.random.random(ansatz.num_parameters)
estimator = Estimator()
local_vqe = VQE(
estimator,
ansatz,
optimizer,
initial_point=initial_point,
)
local_vqe_groundstate_solver = GroundStateEigensolver(qubit_converter, local_vqe)
local_vqe_result = local_vqe_groundstate_solver.solve(problem)
print(local_vqe_result)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--bond_length",
help="Bond length in Angstrom.",
default=2.5,
type=float,
)
args = parser.parse_args()
print(f"Running for bond length {args.bond_length}.")
run(args.bond_length)

View File

@ -0,0 +1,48 @@
from qiskit import QuantumCircuit
from qiskit.circuit.random import random_circuit
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives import Estimator
from quantum_serverless import QuantumServerless, run_qiskit_remote, get, put, save_result
# 1. let's annotate out function to convert it
# to function that can be executed remotely
# using `run_qiskit_remote` decorator
@run_qiskit_remote()
def my_function(circuit: QuantumCircuit, obs: SparsePauliOp):
"""Compute expectation value of an obs given a circuit"""
return Estimator().run([circuit], [obs]).result().values
# 2. Next let's create our serverless object that we will be using to create context
# which will allow us to run funcitons in parallel
serverless = QuantumServerless()
circuits = [random_circuit(2, 2) for _ in range(3)]
# 3. create serverless context which will allow us to run funcitons in parallel
with serverless.context():
# 4. The observable is the same for all expectation value calculations. So we can put that object into remote storage since it will be shared among all executions of my_function.
obs_ref = put(SparsePauliOp(["ZZ"]))
# 5. we can run our function for a single input circuit
# and get back a reference to it as now our function is a remote one
function_reference = my_function(circuits[0], obs_ref)
# 5.1 or we can run N of them in parallel (for all circuits)
function_references = [my_function(circ, obs_ref) for circ in circuits]
# 6. to get results back from reference
# we need to call `get` on function reference
single_result = get(function_reference)
parallel_result = get(function_references)
print("Single execution:", single_result)
print("N parallel executions:", parallel_result)
# 6.1 (Optional) write results to db.
save_result({
"status": "ok",
"single": single_result.tolist(),
"parallel_result": [entry.tolist() for entry in parallel_result]
})

View File

@ -12,6 +12,14 @@ The source code to the project is available `on GitHub <https://github.com/Qiski
**Quickstart**
Step 0: install package
.. code-block::
:caption: pip install
pip install quantum_serverless
Step 1: run infrastructure
.. code-block::
@ -99,6 +107,13 @@ Step 3: run program
------------
**Getting started**
.. toctree::
:maxdepth: 2
Guides <getting_started/index>
**Guides**
.. toctree::

View File

@ -19,6 +19,7 @@ from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import NestedProgram, Job, ComputeResource
from .permissions import IsOwner
from .serializers import ProgramSerializer, JobSerializer

View File

@ -10,5 +10,6 @@ RUN rm -r ./qs
COPY --chown=$NB_UID:$NB_UID ./docs/tutorials/ ./serverless/tutorials/
COPY --chown=$NB_UID:$NB_UID ./docs/guides/ ./serverless/guides/
COPY --chown=$NB_UID:$NB_UID ./docs/getting_started/ ./serverless/getting_started/
ENV JUPYTER_ENABLE_LAB=no

File diff suppressed because it is too large Load Diff