-
Notifications
You must be signed in to change notification settings - Fork 1
How to use
The ProvideQ frontend (main|dev) visualizes the concepts of the toolbox in a user-friendly manner and enables interested users to test the toolbox's capabilities. However, the frontend is not suitable for more advanced use-cases such as batch processing or integration as a component into larger pieces of software.
For programmatic use-cases we offer a full API (main|dev). This is the same API that powers the ProvideQ frontend, which shows that the possible use-cases are endless. One could create a different kind of user interface, integrate it into another website or application, build batch processing functionality on top of the API, or use it for any custom programmatic use case that is relevant to you.
Anyone familiar with a REST API should already have a good understanding of the purpose of the individual endpoints in our API. In essence, the API manages three concepts.
- Problem Type
- Problem Instance
- Solver
For each problem type implemented in the toolbox, we provide a distinct set of endpoints. Examples include the Vehicle Routing Problem or the QUBO Problem.
A problem instance is the core piece that is required to interact with when solving a problem. The API defines ways to create, read, and update a problem. According to the REST API conventions, we define the following endpoints:
– GET /problems/{problemType}
This endpoint is used to get all problems of a problem type that have been created.
– GET /problems/{problemType}/{problemId}
This GET endpoint is a specialized variant that only returns the problem with the requested id.
– POST /problems/{problemType}
The POST request creates a new problem of the specified type.
The request body takes all relevant information, such as the problem input.
Refer to the API specification for all the details.
– PATCH /problems/{problemType}/{problemId}
Using the PATCH requests, it is possible to adjust certain values about the problem. The fields 'input', 'solverId', 'solverSettings', and 'state' can be changed using this request.
The 'state' field must be set to 'SOLVING' in order to start the solving process using the specified solver.
The other fields must be set before the problem state is started.
After this point in time, it is not possible to adjust them.
The 'input' field stores all information about the problem to solve.
The fields for 'solverId' and 'solverSettings' determine what solver to use for the solving process and what settings specific to the solver should
be used.
Each solver is tied to one specific problem type. The purpose of each solver is to take an input of a supported input format and calculate a valid output, however it sees fit. This includes quantum and classical solvers.\
GET /solvers/{problemType}
The possible solvers for a given problem type can be explored via the endpoint.
You will receive a list of solvers with their ids and names.
Additionally, solvers may define any amount of subroutines that they require to solve a problem instance.
The solver can call into their subroutines for use in their solution process.
GET /solvers/{problemType}/{solverId}/sub-routines
Subroutines are solvers themselves, and thus there can be an arbitrary number of nested solvers that are used to solve a problem.
You can list the subroutines of a certain solver via the endpoint.
The response contains a list of subroutine definitions with their typeId and a description.
The description specifies the use of the subroutine in the solving process.
– POST /problems/{problemType}
Furthermore, solvers may define settings which can include aspects such as the number of clusters that a clusterer should create, or an API token that is used to execute a quantum circuit on a quantum backend.
They can be explored via the endpoint GET /solvers/cluster-vrp/{solverId}/settings
– GET /problems/{problemType}/{problemId}/bound
Some problems support the calculation of an upper or lower bound for
their solution. We support this through this special endpoint for more
insight into the quality of the problem solution.
– GET /problems/{problemType}/{problemId}/bound/compare
This endpoint makes it possible to compare the problem's bound with its
actual solution. Note that this is only possible after the problem is
solved and once the bound of a problem is calculated.
We provide a Python library that makes it easy to interact with the API. This library is available here.
We want to solve a VRP problem. For that we have a textual representation of the problem instance in the VRP format.
Our first step is to call the request POST /problems/vrp
to create a
problem with the input set to our problem instance. At this point, the
problem has already been created in the toolbox. In the response of the
initial POST request, we received the id that the toolbox assigned to
our problem. We can always request the current representation of our
problem via GET /problems/vrp/{problemId}
.
Now that we have created the problem, we cannot start it directly. As long as the backend shows the problem state as 'NEEDS_CONFIGURATION', we are missing a piece to start the solving process. In this case, we haven't selected a solverId yet.
To do that, we first need to know which solver we want to use. We can
explore our options using the GET /solvers/vrp
request. For this
example, we want to use the solver which wants to cluster the VRP
problem first and save this solver's id. To do that, we can use a PATCH
request PATCH /problems/vrp/{problemId}
in which we assign the
solverId to the id of the VRP solver that uses clustering. Now, our
problem should be ready to solve. Requesting data on our problem using a
GET request should verify this.
As a next step, we can send another PATCH request and set the 'state' to
'SOLVING' to initiate the solving process. Doing this, one might expect
that, after some time, there will be a solution to our problem. However,
this is not the case. This is because our VRP solver with clustering
defines a subroutine which needs to be configured to cluster the VRP
problem. We can check this by calling
GET /solvers/{problemType}/{solverId}/sub-routines
. Here we can
observe that there is a subroutine of the problem type 'cluster-vrp'.
Similarly, we can see this when checking our problem via
GET /problems/vrp/{problemId}
. There is a list of 'subProblems' that
lists the same subroutine, as well as a list of 'subProblemIds' which
represent the concrete problems that were created to solve the VRP
clustering problem.
Here we can repeat what we did before and check the VRP clustering
problem via GET /problems/cluster-vrp/{subProblemId}
. The problem is
not configured, so we check our solver options via
GET /solvers/cluster-vrp
. For this example, we select the Two Phase
Clusterer to cluster the VRP problem. Using
PATCH /problems/cluster-vrp/{subProblemId}
we set the solverId and the
state to start solving process on the VRP clustering problem.
After starting the clustering process, we will receive another list of
subproblem ids of the various clusters to solve. These are multiple
problems, one for each cluster. In our case with the Two Phase
Clusterer, we received a list of 6 TSP problems. Each of the problems
will need to be configured via the PATCH /problems/tsp/{problemId}
request. However, not every TSP problem needs to be configured in the
same way. Of the 6 problems, we solve 3 of them directly using the Lkh
TSP solver. The other 3 problems are solved via a solver that first
transforms the TSP problem to a QUBO problem. Here we use the
/problems/qubo/{problemId}
request to assign the Qrsip QUBO solver
which uses QAOA.
After all 6 TSP problems have been solved, there are no more steps to
take and all problems are solved to completion. We end up with solutions
for our VRP problem which we can look up via
GET /problems/vrp/{problemId}
.