Serial Solver Execution

You may want to run the solver several times iteratively when evaluating performance. Also, some solvers are more likely to find a good solution by running several iterations with a short timeout than by running a single iteration with a long timeout.Amplify SDK allows running the same combinatorial optimization problem multiple times in a row with the same solver for such needs.

See also

Parallel Solver Execution may be more appropriate for obtaining statistics from multiple runs for formulation, solver performance studies, etc.

Example of multiple runs

First, create a model and solver client as in a usual solve() function execution.

from amplify import VariableGenerator, one_hot, FixstarsClient, solve
from datetime import timedelta

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

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

model = objective + constraint

client = FixstarsClient()
# client.token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
client.parameters.timeout = timedelta(milliseconds=1000)

By passing an integer as the num_solves keyword argument to solve(), the solver is executed iteratively for num_solves times.

result = solve(model, client, num_solves=3)

As in a usual solve() call, the solver returns an instance of the Result class. The return value contains the results of the num_solves iterations.

>>> len(result)
3

Using the best attribute, you can obtain the best solution among those returned by the num_solves runs.

>>> print(f"objective = {result.best.objective}, q = {q.evaluate(result.best.values)}")
objective = -1.0, q = [0. 0. 1.]

Fetching results

When solve() is called with the num_solves keyword, the return value is an instance of the Result class, as usual.

The Amplify SDK determines the number of times of the solver executions by the num_solves attribute of the Result class. This attribute usually yields the same value as the value specified to solve() as the num_solves keyword argument. Still, it may be less than the value specified as the num_solves keyword argument, if some of the solver runs, fail for some reason.

>>> result.num_solves
3

Index or iteration access allows you to aggregate all the solutions the solver returns for the num_solves runs. By default, the Amplify SDK sorts them in order of preferred solution, and there is no distinction regarding the number of executions the solution was found.

>>> print(f"objective = {result[0].objective}, q = {q.evaluate(result[0].values)}")
objective = -1.0, q = [0. 0. 1.]

Use the split property to get only the solutions returned by a particular run.

>>> first_result = result.split[0] # extract only the part of the `result` that was obtained at the first run
>>> type(first_result)
<class 'amplify.Result'>
>>> len(first_result)
1
>>> print(f"objective = {first_result.best.objective}, q = {q.evaluate(first_result.best.values)}")
objective = -1.0, q = [0. 0. 1.]

The following are included for each property of the Result object returned by solve() when num_solves is specified, and the Result object represents the result of the i-th run obtained by using the split property on it.

Property name

Result returned by solve()

Result after applying split

best

The best solution for the num_solves runs

The best solution for the i-th run

solutions

All solutions obtained for the num_solves runs

All solutions obtained for the i-th run

intermediate

Same as usual solve() execution

Same as usual solve() execution

embedding

Same as usual solve() execution

Same as usual solve() execution

client_result

Property obtained for the first run

Property obtained for the i-th run

execution_time

The sum of the num_solves runs

Property obtained for the i-th run

response_time

The sum of the num_solves runs

Property obtained for the i-th run

total_time

Time from the start to the end of solve()

Time used for the i-th run