Serial Solver ExecutionΒΆ

When you evaluate performance, you may want to run the solver multiple times iteratively. Also, for some solvers, multiple short-timeout iterations are more likely to find a good solution than a single long-timeout iteration. The 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 a better fit 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, AmplifyAEClient, 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 = AmplifyAEClient()
# client.token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
client.parameters.time_limit_ms = 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. 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.]

When num_solves is specified, solve() returns a Result object. The following are included for each of its properties. The split property on it gives a Result object that represents the result of the i-th run.

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