Parallel Solver Execution

parallel_solve() can send queries to multiple clients and models simultaneously. When multiple runs are required, such parallel execution may hide some processing times. These are the times for model transformations, request data creation, and data transfer due to network access to the solver. Also, for solvers that can run multiple problems simultaneously, parallel execution is expected to improve execution efficiency.

Parallel execution example

First, construct a model as if using the solve() function.
We will also show examples using multiple models later, so here we will build two models.

from amplify import VariableGenerator, one_hot, solve

gen = VariableGenerator()
q = gen.array("Binary", 3)

objective = q[0] * q[1] - q[2]
constraint = one_hot(q)

model1 = objective + constraint
model2 = objective + 2 * constraint

Next, we create multiple solver clients.

from amplify import AmplifyAEClient, DWaveSamplerClient
from datetime import timedelta

amplify_client = AmplifyAEClient()
# amplify_client.token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
amplify_client.parameters.time_limit_ms = timedelta(milliseconds=1000)

dwave_client = DWaveSamplerClient()
# dwave_client.token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
dwave_client.parameters.num_reads = 100

The parallel_solve() function is used to send requests to multiple clients. parallel_solve() has the same interface as solve(), but allows a list of arguments.

from amplify import parallel_solve

amplify_result, dwave_result = parallel_solve(model1, [amplify_client, dwave_client])

The code above has the same effect as running the following for-loop in parallel.

from amplify import solve

for client in [amplify_client, dwave_client]:
    result = solve(model1, client)

parallel_solve() can also accept lists for both the model and client arguments.
For example, you can use parallel_solve() to run multiple models and multiple clients concurrently as follows:

result1, result2 = parallel_solve([model1, model2], [amplify_client, dwave_client])

Here, the code has the same effect as running the following for-loop in parallel.

for model, client in zip([model1, model2], [amplify_client, dwave_client], strict=True):
    result = solve(model, client)

Note

When both the model and client arguments are given as lists, the number of elements in each list must be the same.

If one of the arguments is a scalar value, it is treated as a list. The same value is repeated for each element of the other argument’s list. In the first example, multiple clients were used for a single model, but you can also use a single client for multiple models as follows:

# The following is equivalent to parallel_solve([model1, model2], [amplify_client] * 2).
result1, result2 = parallel_solve([model1, model2], amplify_client)

# The following is equivalent to parallel_solve([model1] * 2, [amplify_client, dwave_client]).
result1, result2 = parallel_solve(model1, [amplify_client, dwave_client])

Parallel execution parameters

parallel_solve() accepts keyword arguments similar to solve(). Usually, keyword arguments are common for all parallel runs. But by providing a list, you can specify multiple keyword arguments simultaneously, as with multiple models and clients. In this case as well, the list-type keyword arguments must have the same number of elements. This number must equal that of the model and client arguments.

As an example, consider the following parameter specification using lists:

amplify_result, dwave_result = parallel_solve(
    model1,
    [amplify_client, dwave_client],
    dry_run=[False, True],
    num_solves=2,
)

The above has the same effect as running the following code in parallel.

for client, dry_run in zip([amplify_client, dwave_client], [False, True], strict=True):
    result = solve(model1, client, dry_run=dry_run, num_solves=2)

Depending on the solver type and contract, sending multiple requests to the same client simultaneously may not be possible. You can set the maximum number of parallel runs by specifying the concurrency parameter of parallel_solve(). The default is 0, where the number of parallel runs is automatically determined by the number of CPUs on the machine running it.

amplify_result, dwave_result = parallel_solve(
    model1,
    [amplify_client, dwave_client],
    concurrency=2,
)