{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Dirac-3 Developer Beginner Guide\n", "\n", "Dirac-3 is the latest addition to QCi's Entropy Quantum Computing (EQC) product line, a unique quantum-hardware approach for tackling hard polynomial optimization problems. Dirac-3 uses non-binary _qudits_ as its unit of quantum information (cf. binary qubits), where each quantum state is represented by $d \\geq 2$ dimensions. Dirac-3 can solve problems with variables beyond binary $(0, 1)$, including non-negative integers and sum-constrained continuous numbers. For further information on qudits, please read through the [Qudit Primer](https://learn.quantumcomputinginc.com/learn/lessons/qudit-basics) to better understand the benefits of high-dimensional programming. To delve deeper into the underlying physics of EQC technology, refer to our paper: [An Open Quantum System for Discrete Optimization](https://arxiv.org/abs/2407.04512).\n", "\n", "Discrete-based variables allow Dirac-3 to solve a variety of important problems including integer-optimization problems with higher-order-polynomial objective functions. This tutorial provides an introduction to how to formulate such problems for Dirac-3 and run them as _jobs_ via QCi's cloud-based REST API. Dirac-3 is a stochastic solver that does not necessarily find the same solution on every run. Thus, the general approach is to use multiple runs per job to \"sample\" a solution space that we configure to be large enough to contain the global minimum (a.k.a., the ground state) we seek for the problem at hand.\n", "\n", "**Prerequisites**: In order to begin running problems with Dirac-3 you will need to: \n", "- [Obtain an API access token](https://quantumcomputinginc.com/learn/tutorials-and-use-cases/quick-start-on-cloud)\n", "- [Install qci-client (for Python)](https://quantumcomputinginc.com/learn/tutorials-and-use-cases/quick-start-on-cloud#installation)\n", "\n", "The following code block initializes a `QciClient` client (from the [`qci-client`](https://pypi.org/project/qci-client/) Python package imported as `qci_client`) that connects to a Dirac-3 device via QCi's REST API on the cloud (at `url`). As with all of `QciClient`'s API-request methods, the `client`'s `get_allocations` method checks for a non-expired _access_ token from the API, and automatically retrieves/refreshes it when needed (using `api_token` as the _refresh_ token). A successful `get_allocations` call verifies both your authenticated API connectivity and your allocation time (in seconds) for running jobs on Dirac-3. Run this code block successfully (with `api_token` updated to your API token) before attempting to run the subsequent example problems." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import importlib.metadata\n", "from pprint import pprint\n", "\n", "from qci_client import JobStatus, QciClient\n", "\n", "# Requires qci-client v4.5.0+.\n", "print(f\"qci-client v{importlib.metadata.version('qci-client')}\")\n", "\n", "url = \"https://api.qci-prod.com\"\n", "api_token = \"\"\n", "client = QciClient(url=url, api_token=api_token)\n", "\n", "print(client.get_allocations()[\"allocations\"][\"dirac\"])\n", "# This should output something similar to:\n", "# qci-client v4.5.0\n", "# {'metered': True, 'seconds': 600}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Polynomial Objective Functions\n", "\n", "Dirac-3 solves objective-function-minimization problems over discrete spaces by searching for the ground state (i.e., lowest value) for the total \"energy\" of a potentially complex system with many variables with multiplicative interactions of different orders. Generally, this corresponds to finding the set of variables $x_i, \\: i \\in \\{1, \\ldots, N\\}$ that minimizes the energy $E$ returned by the following $N$-variable polynomial objective function up to degree five:\n", "$$\n", "E = \\sum_{i=1}^{N} C_i x_i +\n", "\\sum_{i,j=1}^{N,N} J_{ij} x_i x_j +\n", "\\sum_{i,j,k=1}^{N,N,N} T_{ijk} x_i x_j x_k +\n", "\\sum_{i,j,k,l=1}^{N,N,N,N} Q_{ijkl} x_i x_j x_k x_l +\n", "\\sum_{i,j,k,l,m=1}^{N,N,N,N,N} P_{ijklm} x_i x_j x_k x_l x_m,\n", "$$\n", "where $C_{i}$ is the real-valued linear return of each variable, and $J_{ij}$, $T_{ijk}$, $Q_{ijkl}$, and $P_{ijklm}$ are real-valued joint returns of variables (a.k.a., interaction terms). Constant terms are omitted as they do not affect the location of minima.\n", "\n", "Dirac-3 allows direct submission for minimization only. It is assumed that users perform a simple transformation before submitting the problem to handle maximization (i.e., multiply coefficients by -1). For additional information please refer to our [Dirac-3 User Guide](https://quantumcomputinginc.com/learn/spec-sheets/dirac-3-users-guide)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Encoding Polynomials\n", "\n", "Encoding a polynomial objective function is important for correctly defining a problem. Consider the degree-three polynomial in two variables $x_1$, $x_2$:\n", "$$\n", "E = 3.14 - 2 x_1 + 3 x_2 - 4 x_1^2 + 5 x_1 x_2 -6 x_1 x_2 + 7 x_1^2 x_2 - 8 x_2^3.\n", "$$\n", "The terms of this polynomial are encoded separately as lists of coefficients and indices. The (real-valued) coefficients of this polynomial are encoded as\n", "\n", "`poly_coefs = [3.14, -2, 3, -4, 5, -6, 7, -8]`,\n", "\n", "whereas the degree of the term corresponding to each coefficient is encoded using a nested list of indices as\n", "\n", "`poly_indices = [[0, 0, 0], [0, 0, 1], [0, 0, 2], [0, 1, 1], [0, 1, 2], [0, 1, 2], [1, 1, 2], [2, 2, 2]]`.\n", "\n", "As long as the elements of `poly_coefs` and `poly_indices` correspond to each other in the encoding, the ordering of polynomial terms does not matter. Each index list in `poly_indices` should have a length corresponding to the maximum degree of the polynomial. The elements of each index list refer to the 1-based variable number, with repeats corresponding to a higher degree for that variable and zero(s) used to specify a lower degree term. All index elements must be non-decreasing from left to right, which ensures a unique representation of each polynomial term. Missing terms of a given degree are simply omitted from both lists, and multiple terms of the same degree are allowed and will be simply summed as indicated (e.g., the $5 x_1 x_2$ and $-6 x_1 x_2$ terms). The all-zero $[0, 0, 0]$ index refers to a degree-zero constant term (here $3.14$), which is normally omitted as it does not affect the minimization and is currently _not_ supported by Dirac-3." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Dirac-3 as an Integer Solver\n", "\n", "When used as an integer solver, Dirac-3's qudits are configured so that each represents an _integer_ variable $x_i$ with a user-defined integer-valued upper bound $Z_i \\geq 1$, i.e.,\n", "$$\n", "0 \\leq x_i \\leq Z_i \\quad \\forall i \\in \\{1, \\ldots, N\\},\n", "$$\n", "where $Z_i + 1$ is called the _number of levels_ for variable $i$.\n", "\n", "Note that taking $Z_i = 1$ for all $i \\in \\{1, \\ldots, N\\}$ and $C_i = T_{ijk} = Q_{ijkl} = P_{ijklm} = 0$ reduces the problem formulation to be equivalent to the common quadratic unconstrained binary optimization (QUBO), which Dirac-3 can solve." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Formulating and Running Integer Problems on Dirac-3\n", "\n", "We define a basic integer-optimization problem for submission to Dirac-3. To illustrate a standard problem submission, a polynomial is used that has multiple local minima surrounding a global minumum at the origin. We intend to find the non-negative integer values of $x_1$ and $x_2$ that minimize the energy $E$ defined by:\n", "$$\n", "E = \\frac{1}{4} \\left( x^4_1 + x^4_2 \\right) - \\frac{5}{3} \\left( x^3_1 + x^3_2 \\right) + 3 \\left( x^2_1 + x^2_2 \\right),\n", "$$\n", "where $x_1$ has five levels $\\{0,1,2,3,4\\}$ and $x_2$ has three levels $\\{0,1,2\\}$.\n", "\n", "For the purposes of illustration, this simple polynomial does not have \"mixed terms\" involving the product of $x_1$ and $x_2$. In the positive quadrant, this function is known to have a global minimum at $(0,0)$ with energy $0$, and local minima at $(0, 3)$, $(3, 0)$, and $(3, 3)$ with energies $2.25$, $2.25$, and $4.5$, respectively." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Uploading the Problem File\n", "\n", "The first step is to extract the polynomial coefficients and format polynomial variable indices for each term in the equation. The termwise polynomial coefficients will be represented as a list as follows:\n", "\n", "`poly_coefs = [0.25, 0.25, -1.6666667, -1.6666667, 3, 3]`.\n", "\n", "The polynomial indices for the coefficients will be represented as follows in the same order of terms as represented in the polynomial coefficients:\n", "\n", "`poly_indices = [[1, 1, 1, 1], [2, 2, 2, 2], [0, 1, 1, 1], [0, 2, 2, 2], [0, 0, 1, 1], [0, 0, 2, 2]]`.\n", "\n", "We'll use `poly_coeffs` and `poly_indices` to generate the data file. We will then use the `client` that we initialized above to upload the file via the API. To allow the API to validate the polynomial, we explicitly provide the polynomial's `min_degree`, `max_degree`, and `num_variables`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Let's consider a simple Hamiltonian problem\n", "poly_indices_int_problem = [[1, 1, 1, 1], [2, 2, 2, 2], [0, 1, 1, 1], [0, 2, 2, 2], [0, 0, 1, 1], [0, 0, 2, 2]]\n", "poly_coefs_int_problem = [0.25, 0.25, -1.6666667, -1.6666667, 3, 3]\n", "data_int_problem = [{\"idx\": idx, \"val\": val} for idx, val in zip(poly_indices_int_problem, poly_coefs_int_problem)]\n", "file_int_problem = {\n", " \"file_name\": \"dirac_3_integer_example\",\n", " \"file_config\": {\n", " \"polynomial\": {\n", " \"num_variables\": 2,\n", " \"min_degree\": 2,\n", " \"max_degree\": 4,\n", " \"data\": data_int_problem,\n", " }\n", " }\n", "}\n", "\n", "file_response_int_problem = client.upload_file(file=file_int_problem)\n", "file_response_int_problem\n", "# This should output something similar to:\n", "# {'file_id': '670ff26b5e0855263226e27f'}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Specifying Job Body Parameters\n", "\n", "We now build the job body, selecting from the following configuration parameters:\n", "\n", "* `job_type`: The type of job to be performed. In this case, ’sample-hamiltonian-integer’ indicates that the job minimizes a Hamiltonian with integer variables.\n", "\n", "* `job_name`: An optional user-defined string that names the job. Here, it’s set to ’test_integer_variable_hamiltonian_job’.\n", "\n", "* `job_tags`: An optional list of user-defined strings to tag the job for easier reference and organization. In this example, the tags are [’tag1’, ’tag2’].\n", "\n", "* `job_params`: A dictionary containing parameters for configuring the job's problem and device. The relevant fields generally depend on the choice of `job_type` and `device_type`, the latter of which must be present in `job_params` and here is ’dirac-3’. For ’sample-hamiltonian-integer’ on ’dirac-3’, the following `job_params` fields are relevant:\n", " * `num_samples`: The optional number of samples to run for the stochastic solver. The value must be between 1 and 100, with default 1.\n", " * `relaxation_schedule`: A configuration selector that must be in the set $\\{1, 2, 3, 4\\}$, representing four different schedules. The relaxation schedule controls multiple parameters of the quantum machine including the amount of loss, number of feedback loops, the amount of quantum fluctuation, and mean photon number measured. While the first two parameters are fixed, the last two can be further adjusted by users (see below). Lower relaxation schedules are set to larger amount of dissipation in a open quantum system setup, leading to more iterations needed to reach stable states. As a result, the probability of finding an optimal solution can be higher in higher schedules, especially on a competitive energy landscape with the trade-off of longer evolution time. This parameter is optional with a default of 1.\n", " * `num_levels`: An array of positive integers that defines the maximum possible value for each variable. For example, $[2, 2, 3]$ means $x_1$ and $x_2$ take two-level binary values 0 or 1 (qubit) and $x_3$ takes a three-level value 0, 1, or 2 (qudit). If each variable has the same upper bound, then an array with only a single value may be passed. The job will error if the total of `num_levels` over all variables exceeds the device limit (currently 954 on Dirac-3).\n", " * `mean_photon_number`: An optional advanced configuration parameter that is normally set automatically through the choice of `relaxation_schedule`. A value specified here overrides the default value and should be a real-number value from 0.001 to 1. This parameter is the average number of photons detected over a specific time period which is a time-bin representing a possible value of a variable in Dirac-3. This is a common metric used in photon statistics and quantum optics to approximate the probability of being in the single-photon regime of coherent light. Low mean photon number maintains the quantum superposition effect in high-dimensional time-bin modes of the wavefunction. Notice that extremely low mean photon number to the same order of thermal or electronic noise in single photon detector might affect the solution negatively. [Fox, M. (2006). _Quantum Optics_. Wiley, 75-104., [Pearson, D., Elliott, C. (2004). On the Optimal Mean Photon Number for Quantum Cryptography.](https://arxiv.org/abs/quant-ph/0403065v2), [Mower, J., Zhang, Z., Desjardins, P., Lee, C., Shapiro, J. H., Englund, D. (2013). High-dimensional quantum key distribution using dispersive optics. Phys. Rev. A, 87(6), 062322.](https://link.aps.org/doi/10.1103/PhysRevA.87.062322), [Nguyen, L., Rehain, P., Sua, Y. M., Huang, Y. (2018). Programmable quantum random number generator without postprocessing. Opt. Lett., 43(4), 631-634.](https://opg.optica.org/ol/abstract.cfm?URI=ol-43-4-631)]\n", " * `quantum_fluctuation_coefficient`: An optional advanced configuration parameter that is normally set automatically through the choice of `relaxation_schedule`. A value specified here overrides the default value and should be an integer value $n \\in \\{1, 2, \\ldots, 50\\}$, which is used to compute the coefficient $\\frac{1}{\\sqrt{n}}$ in the real-valued interval $\\left[ \\frac{1}{\\sqrt{50}}, 1 \\right]$. The inherent randomness of photon arrival time comes from the quantum nature of light giving rise to a fundamental limitation in single photon counting, known as Poisson noise. Dirac-3 takes advantage of this noise arising from quantum fluctuation to gain opportunity to large search space and jump out of local minima. This parameter can be adjusted to allow high or low amount of quantum fluctuation into the open system. A low fluctuation tends to provide a worse solution than a high fluctuation. Notice that, to maintain a good returned solution, this parameter should not reach too high in the same order as the signal photon. [[Bédard, G. (1967). Analysis of Light Fluctuations from Photon Counting Statistics, J. Opt. Soc. Am., 57, 1201-1206.](https://doi.org/10.1364/JOSA.57.001201)]\n", "\n", "* `polynomial_file_id`: The unique identifier for the uploaded polynomial file, retrieved from the file response's `file_id`. This ID links the job to the specific problem data.\n", "\n", "By preparing the job body in this manner, you set up all necessary configurations and metadata required by the QCi API to process the integer-optimization task on the Dirac-3 device." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Build the job body to be submitted to the QCi API.\n", "# This is where the job type and the Dirac-3 device and its configuration are specified.\n", "job_body_int_problem = client.build_job_body(\n", " job_type='sample-hamiltonian-integer',\n", " job_name='test_integer_variable_hamiltonian_job', # user-defined string, optional\n", " job_tags=['tag1', 'tag2'], # user-defined list of string identifiers, optional\n", " job_params={\n", " 'device_type': 'dirac-3',\n", " 'num_samples': 5,\n", " 'relaxation_schedule': 1,\n", " 'num_levels': [5, 2], # For demonstration, this excludes some but not all of the known local minima.\n", " },\n", " polynomial_file_id=file_response_int_problem['file_id'],\n", ")\n", "pprint(job_body_int_problem)\n", "# This should output something similar to:\n", "# {'job_submission': {'device_config': {'dirac-3_qudit': {'num_levels': [5, 2],\n", "# 'num_samples': 5,\n", "# 'relaxation_schedule': 1}},\n", "# 'job_name': 'test_integer_variable_hamiltonian_job',\n", "# 'job_tags': ['tag1', 'tag2'],\n", "# 'problem_config': {'qudit_hamiltonian_optimization': {'polynomial_file_id': '674f27a75e08552632271ef7'}}}}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Submitting the Job to the API and Running It\n", "\n", "Now using the job body that we just created we'll submit the job to QCi's REST API. After submission your job will progress through the following states:\n", "\n", "- **QUEUED**: waiting for the Dirac-3 to become available\n", "- **RUNNING**: the job has been submitted to the Dirac-3 and is running\n", "- **COMPLETED**: the job has completed and results are available for analysis" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Submit the job and await the result.\n", "job_response_int_problem = client.process_job(job_body=job_body_int_problem)\n", "# Before inspecting solution(s), ensure that job did not error.\n", "assert job_response_int_problem[\"status\"] == JobStatus.COMPLETED.value\n", "# Ten samples taken.\n", "print(\"Found solutions:\")\n", "pprint(job_response_int_problem['results']['solutions'])\n", "print(\"with energies:\")\n", "pprint(job_response_int_problem['results']['energies'])\n", "print(\"and counts:\")\n", "pprint(job_response_int_problem['results']['counts'])\n", "\n", "# This should output something similar to:\n", "# 2024-12-03 09:00:11 - Dirac allocation balance = 0 s (unmetered)\n", "# 2024-12-03 09:00:11 - Job submitted: job_id='674f2b0b324e163bf2d890c7'\n", "# 2024-12-03 09:00:11 - QUEUED\n", "# 2024-12-03 09:00:14 - RUNNING\n", "# 2024-12-03 09:00:24 - COMPLETED\n", "# 2024-12-03 09:00:26 - Dirac allocation balance = 0 s (unmetered)\n", "# Found solutions:\n", "# [[0, 0]]\n", "# with energies:\n", "# [0]\n", "# and counts:\n", "# [5]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Inspecting Detailed Results\n", "\n", "In the Python code above we printed the most relevant solution information for the ten samples, which are lists of:\n", "* solution - a vector representing the solution to the problem from a given run on the Dirac hardware\n", "* energy - the objective value for solution returned by the device\n", "* count - the number of times that the solution was observed (esp. relevant when `num_samples > 1`)\n", "\n", "The full job response from the client has more information, as seen in the `job_response` variable below." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pprint(job_response_int_problem)\n", "\n", "# This should output something similar to:\n", "# {'job_info': {'job_id': '674f5de4324e163bf2d890fa',\n", "# 'job_result': {'device_usage_s': 8,\n", "# 'file_id': '674f5e1b5e08552632271fd7'},\n", "# 'job_status': {'completed_at_rfc3339nano': '2024-12-03T19:38:03.837Z',\n", "# 'queued_at_rfc3339nano': '2024-12-03T19:37:08.037Z',\n", "# 'running_at_rfc3339nano': '2024-12-03T19:37:08.315Z',\n", "# 'submitted_at_rfc3339nano': '2024-12-03T19:37:08.036Z'},\n", "# 'job_submission': {'device_config': {'dirac-3_qudit': {'num_levels': [5,\n", "# 2],\n", "# 'num_samples': 10,\n", "# 'relaxation_schedule': 1}},\n", "# 'job_name': 'test_integer_variable_hamiltonian_job',\n", "# 'job_tags': ['tag1', 'tag2'],\n", "# 'problem_config': {'qudit_hamiltonian_optimization': {'polynomial_file_id': '674f29fc5e08552632271f09'}}}},\n", "# 'results': {'counts': [10], 'energies': [0], 'solutions': [[0, 0]]},\n", "# 'status': 'COMPLETED'}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In addition to the solutions and energies, `job_response` contains additional information that may be useful:\n", "- `job_id`: The unique id of the job that was run\n", "- `job_submission`->`problem_config`: contains all the information about the problem that was configured for the job\n", "- `job_submission`->`device_config`: contains all the information about the device that was configured for the job\n", "- `job_result`->`file_id`: the unique id of the results file that stores the job's result (otherwise has `error` field with error message)\n", "- `job_result`->`device_usage_s`: the amount of \"billable\" time used on the device\n", "- `results`: the solutions and corresponding energies (and their corresponding counts) that were found for the job" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Dirac-3 as a Continuous Solver\n", "\n", "When used as a continuous solver, Dirac-3 are configured so that each represents a _non-negative continuous_ variable $x_i$ with a user-defined sum constraint, i.e.,\n", "$$\n", "\\sum_{i=1}^N x_i = R \\text{\\quad with } 1 \\leq R \\leq 10000 \\text{\\quad and \\quad} x_i \\geq 0 \\quad \\forall i \\in \\{1, \\ldots, N\\}.\n", "$$\n", "Here, $x_{i}$ is real-valued with an expected resolution of $\\frac{R}{\\text{dynamic range}}$, where $R$ is the real-valued summation constraint. Dirac-3 currently provides a maximum $\\text{dynamic range}$ of about $200$." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Formulating and Running Continuous Problems on Dirac-3\n", "\n", "We define a basic continuous-optimization problem for submission to Dirac-3. In order to illustrate a standard problem submission, a simple polynomial problem will be utilized. We intend to find the values of $x_1$, $x_2$, $x_3$, and $x_4$ that minimize the energy $E$ defined by:\n", "$$\n", "E = 3 x_4 + 2.1 x^2_1 + 1.5 x^2_2 + 7.9 x_2 x_3 + x_2 x_4^2 + x^3_3,\n", "$$\n", "with sum constraint $x_1 + x_2 + x_3 + x_4 = 1$ (i.e., $R=1$)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Uploading the Problem File\n", "\n", "The first step is to extract the polynomial coefficients and format polynomial variable indices for each term in the equation. The polynomial coefficients will be represented as a list as follows:\n", "\n", "`poly_coefs = [3, 2.1, 1.5, 7.9, 1, 1]`\n", "\n", "The polynomial indices for the coefficients will be represented as follows in the same order as represented in the original equation:\n", "\n", "`poly_indices = [[0,0,4], [0,1,1], [0,2,2], [0,2,3], [2,4,4], [3,3,3]]`\n", "\n", "We'll use `poly_coeffs` and `poly_indices` to generate the data file. We will then use the `client` that we initialized above to upload the file via the API. To allow the API to validate the polynomial, we explicitly provide the polynomial's `min_degree`, `max_degree`, and `num_variables`. (Explicitly specifying the problem's `num_variables` can be especially important for this problem type when the problem contains an extra \"slack variable\" that does not explicitly appear in any polynomial terms.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Let's consider a simple Hamiltonian problem\n", "poly_indices_cts_problem = [[0,0,4], [0,1,1], [0,2,2], [0,2,3], [2,4,4], [3,3,3]]\n", "poly_coefs_cts_problem = [3, 2.1, 1.5, 7.9, 1, 1]\n", "data_cts_problem = [{\"idx\": idx, \"val\": val} for idx, val in zip(poly_indices_cts_problem, poly_coefs_cts_problem)]\n", "file_cts_problem = {\n", " \"file_name\": \"dirac_3_continuous_variable_example\",\n", " \"file_config\": {\n", " \"polynomial\": {\n", " \"num_variables\": 4,\n", " \"min_degree\": 1,\n", " \"max_degree\": 3,\n", " \"data\": data_cts_problem,\n", " }\n", " }\n", "}\n", "\n", "file_response_cts_problem = client.upload_file(file=file_cts_problem)\n", "file_response_cts_problem\n", "# This should output something similar to:\n", "# {'file_id': '670ff26b5e0855263226e27f'}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Specifying Job Body Parameters\n", "\n", "We now build the job body, selecting from the following configuration parameters:\n", "\n", "* `job_type`: Specifies the type of job to be performed. In this case, ’sample-hamiltonian’ indicates that the job minimizes a Hamiltonian with continuous non-negative variables obeying a sum constraint.\n", "\n", "* `job_name`: An optional user-defined string that names the job. Here, it’s set to ’dirac_3_continuous_variable_example’.\n", "\n", "* `job_tags`: An optional list of user-defined string identifiers to tag the job for easier reference and organization. In this example, the tags are [’tag1’, ’tag2’].\n", "\n", "* `job_params`: A dictionary containing parameters for configuring the problem and the device. The relevant fields generally depend on the choice of problem and `device_type`, which must be present and here is ’dirac-3’. For this problem on this device, the following `job_params` fields are relevant:\n", " * `num_samples`: The optional number of samples to run for the stochastic solver. The value must be between 1 and 100, with default 1.\n", " * `relaxation_schedule`: A configuration selector that must be in the set $\\{1, 2, 3, 4\\}$, representing four different schedules. The relaxation schedule controls multiple parameters of the quantum machine including the amount of loss, number of feedback loops, the amount of quantum fluctuation, and mean photon number measured. While the first two parameters are fixed, the last two can be further adjusted by users (see below). Lower relaxation schedules are set to larger amount of dissipation in a open quantum system setup, leading to more iterations needed to reach stable states. As a result, the probability of finding an optimal solution can be higher in higher schedules, especially on a competitive energy landscape with the trade-off of longer evolution time. This parameter is optional with a default of 1.\n", " * `sum_constraint`: A constraint applied to the problem space such that the solution variables must sum to the provided value. Optional value that must be between 1 and 10000, with default 1.\n", " * `solution_precision`: An optional number that specifies the level of precision to apply to the solutions. Omit this when the highest precision continuous solutions are deisired. If specified, then a distillation method is applied to the continuous solutions to reduce them to the submitted `solution_precision`. Note that `sum_constraint` must be divisible by `solution_precision` when the latter is specified.\n", " * `mean_photon_number`: An optional advanced configuration parameter that is normally set automatically through the choice of `relaxation_schedule`. A value specified here overrides the default value and should be a real-number value from 0.001 to 1. This parameter is the average number of photons detected over a specific time period which is a time-bin representing a possible value of a variable in Dirac-3. This is a common metric used in photon statistics and quantum optics to approximate the probability of being in the single-photon regime of coherent light. Low mean photon number maintains the quantum superposition effect in high-dimensional time-bin modes of the wavefunction. Notice that extremely low mean photon number to the same order of thermal or electronic noise in single photon detector might affect the solution negatively. [Fox, M. (2006). _Quantum Optics_. Wiley, 75-104., [Pearson, D., Elliott, C. (2004). On the Optimal Mean Photon Number for Quantum Cryptography.](https://arxiv.org/abs/quant-ph/0403065v2), [Mower, J., Zhang, Z., Desjardins, P., Lee, C., Shapiro, J. H., Englund, D. (2013). High-dimensional quantum key distribution using dispersive optics. Phys. Rev. A, 87(6), 062322.](https://link.aps.org/doi/10.1103/PhysRevA.87.062322), [Nguyen, L., Rehain, P., Sua, Y. M., Huang, Y. (2018). Programmable quantum random number generator without postprocessing. Opt. Lett., 43(4), 631-634.](https://opg.optica.org/ol/abstract.cfm?URI=ol-43-4-631)]\n", " * `quantum_fluctuation_coefficient`: An optional advanced configuration parameter that is normally set automatically through the choice of `relaxation_schedule`. A value specified here overrides the default value and should be an integer value $n \\in \\{1, 2, \\ldots, 50\\}$, which is used to compute the coefficient $\\frac{1}{\\sqrt{n}}$ in the real-valued interval $\\left[ \\frac{1}{\\sqrt{50}}, 1 \\right]$. The inherent randomness of photon arrival time comes from the quantum nature of light giving rise to a fundamental limitation in single photon counting, known as Poisson noise. Dirac-3 takes advantage of this noise arising from quantum fluctuation to gain opportunity to large search space and jump out of local minima. This parameter can be adjusted to allow high or low amount of quantum fluctuation into the open system. A low fluctuation tends to provide a worse solution than a high fluctuation. Notice that, to maintain a good returned solution, this parameter should not reach too high in the same order as the signal photon. [[Bédard, G. (1967). Analysis of Light Fluctuations from Photon Counting Statistics, J. Opt. Soc. Am., 57, 1201-1206.](https://doi.org/10.1364/JOSA.57.001201)]\n", "\n", "* `polynomial_file_id`: The unique identifier for the uploaded polynomial file, retrieved from the file response's `file_id`. This ID links the job to the specific problem data.\n", "\n", "By preparing the `job_body` in this manner, you set up all necessary configurations and metadata required by the QCi API to process the sum-contrained continuous-optimization task on the Dirac-3 device." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Build the job body to be submitted to the QCi API.\n", "# This is where the job type and the Dirac-3 device and its configuration are specified.\n", "job_body_cts_problem = client.build_job_body(\n", " job_type='sample-hamiltonian',\n", " job_name='test_continuous_variable_hamiltonian_job', # user-defined string, optional\n", " job_tags=['tag1', 'tag2'], # user-defined list of string identifiers, optional\n", " job_params={\n", " 'device_type': 'dirac-3',\n", " 'relaxation_schedule': 1,\n", " 'sum_constraint': 1,\n", " },\n", " polynomial_file_id=file_response_cts_problem['file_id'],\n", ")\n", "pprint(job_body_cts_problem)\n", "# This should output something similar to:\n", "# {'job_submission': {'device_config': {'dirac-3_normalized_qudit': {'relaxation_schedule': 1,\n", "# 'sum_constraint': 1}},\n", "# 'job_name': 'test_continuous_variable_hamiltonian_job',\n", "# 'job_tags': ['tag1', 'tag2'],\n", "# 'problem_config': {'normalized_qudit_hamiltonian_optimization': {'polynomial_file_id': '670ff2765e0855263226e283'}}}}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Submitting the Job to the API and Running It" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Submit the job and await the result.\n", "job_response_cts_problem = client.process_job(job_body=job_body_cts_problem)\n", "# Before inspecting solution, ensure that job did not error.\n", "assert job_response_cts_problem[\"status\"] == JobStatus.COMPLETED.value\n", "# Only one sample taken.\n", "print(\n", " f\"solution: {job_response_cts_problem['results']['solutions'][0]} with \" \n", " f\"energy: {job_response_cts_problem['results']['energies'][0]}\"\n", ")\n", "\n", "# This should output something similar to:\n", "# 2024-05-15 10:59:49 - Dirac allocation balance = 600 s\n", "# 2024-05-15 10:59:49 - Job submitted: job_id='6644ea05d448b017e54f9663'\n", "# 2024-05-15 10:59:49 - QUEUED\n", "# 2024-05-15 10:59:52 - RUNNING\n", "# 2024-05-15 11:00:46 - COMPLETED\n", "# 2024-05-15 11:00:48 - Dirac allocation balance = 599 s\n", "# solution: 0.4174435, 0.5825538, 0, 0] with energy: 0.8749975" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Inspecting Detailed Results" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pprint(job_response_cts_problem)\n", "\n", "# This should output something similar to:\n", "# {'job_info': {'job_id': '6750b4b4324e163bf2d89395',\n", "# 'job_result': {'device_usage_s': 1,\n", "# 'file_id': '6750b4b65e08552632272819'},\n", "# 'job_status': {'completed_at_rfc3339nano': '2024-12-04T19:59:50.632Z',\n", "# 'queued_at_rfc3339nano': '2024-12-04T19:59:48.302Z',\n", "# 'running_at_rfc3339nano': '2024-12-04T19:59:48.679Z',\n", "# 'submitted_at_rfc3339nano': '2024-12-04T19:59:48.302Z'},\n", "# 'job_submission': {'device_config': {'dirac-3_normalized_qudit': {'num_samples': 1,\n", "# 'relaxation_schedule': 1,\n", "# 'sum_constraint': 1}},\n", "# 'job_name': 'test_continuous_variable_hamiltonian_job',\n", "# 'job_tags': ['tag1', 'tag2'],\n", "# 'problem_config': {'normalized_qudit_hamiltonian_optimization': {'polynomial_file_id': '6750b4ab5e08552632272817'}}}},\n", "# 'results': {'counts': [1],\n", "# 'energies': [0.8749975],\n", "# 'solutions': [[0.4174435, 0.5825538, 0, 0]]},\n", "# 'status': 'COMPLETED'}" ] } ], "metadata": { "kernelspec": { "display_name": "debug", "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.10.7" } }, "nbformat": 4, "nbformat_minor": 4 }