Skip to content
This repository has been archived by the owner on Dec 7, 2021. It is now read-only.

Warm start ADMM #1202

Merged
merged 10 commits into from
Aug 17, 2020
30 changes: 30 additions & 0 deletions qiskit/optimization/algorithms/admm_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def __init__(self,
tau_decr: float = 2,
mu_res: float = 10,
mu_merit: float = 1000,
warm_start: bool = False,
max_iter: Optional[int] = None) -> None:
"""Defines parameters for ADMM optimizer and their default values.

Expand All @@ -76,6 +77,11 @@ def __init__(self,
tau_decr: Parameter used in the rho update (UPDATE_RHO_BY_RESIDUALS).
mu_res: Parameter used in the rho update (UPDATE_RHO_BY_RESIDUALS).
mu_merit: Penalization for constraint residual. Used to compute the merit values.
warm_start: Start ADMM with pre-initialized values for binary and continuous variables
woodsp-ibm marked this conversation as resolved.
Show resolved Hide resolved
by solving a relaxed (all variables are continuous) problem first. This option does
not guarantee the solution will optimal or even feasible. The option should be
used when tuning other options does not help and should be considered as a hint
to the optimizer where to start its iterative process.
max_iter: Deprecated, use maxiter.
"""
super().__init__()
Expand All @@ -97,6 +103,7 @@ def __init__(self,
self.factor_c = factor_c
self.beta = beta
self.rho_initial = rho_initial
self.warm_start = warm_start

def __repr__(self) -> str:
props = ", ".join(["{}={}".format(key, value) for (key, value) in vars(self).items()])
Expand Down Expand Up @@ -292,6 +299,9 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult:
self._state.binary_indices = self._get_variable_indices(problem, Variable.Type.BINARY)
self._state.continuous_indices = self._get_variable_indices(problem,
Variable.Type.CONTINUOUS)
if self._params.warm_start:
# warm start injection for the initial values of the variables
self._warm_start(problem)

# convert optimization problem to a set of matrices and vector that are used
# at each iteration.
Expand Down Expand Up @@ -830,6 +840,26 @@ def _get_solution_residuals(self, iteration: int) -> Tuple[float, float]:

return primal_residual, dual_residual

def _warm_start(self, problem: QuadraticProgram) -> None:
"""Solves a relaxed (all variables are continuous) and initializes the optimizer state with
the found solution.

Args:
problem: a problem to solve.

Returns:
None
"""
qp_copy = copy.deepcopy(problem)
for variable in qp_copy.variables:
variable.vartype = VarType.CONTINUOUS
cts_result = self._continuous_optimizer.solve(qp_copy)
logger.debug("Continuous relaxation: %s", cts_result.x)

self._state.x0 = cts_result.x[self._state.binary_indices]
self._state.u = cts_result.x[self._state.continuous_indices]
self._state.z = cts_result.x[self._state.binary_indices]

@property
def parameters(self) -> ADMMParameters:
"""Returns current parameters of the optimizer.
Expand Down
5 changes: 5 additions & 0 deletions releasenotes/notes/admm-optimizer-d482b33f2474244e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ features:
Removed soft dependency on CPLEX in ADMMOptimizer. Now default optimizers used by ADMMOptimizer
are MinimumEigenOptimizer for QUBO problems and SlsqpOptimizer as a continuous optimizer. You
can still use CplexOptimizer as an optimizer for ADMMOptimizer, but it should be set explicitly.
- |
woodsp-ibm marked this conversation as resolved.
Show resolved Hide resolved
Introduced an option `warm_start` that should be used when tuning other options does not help.
When this option is enabled, a relaxed problem (all variables are continuous) is solved first
and the solution is used to initialize the state of the optimizer before its starts the
iterative process in the `solve` method.
34 changes: 34 additions & 0 deletions test/optimization/test_admm.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,40 @@ def test_admm_ex5(self):
self.assertIsNotNone(solution.state)
self.assertIsInstance(solution.state, ADMMState)

def test_admm_ex5_warm_start(self):
"""Example 5 but with a warm start"""
mdl = Model('ex5')

# pylint:disable=invalid-name
v = mdl.binary_var(name='v')
w = mdl.binary_var(name='w')
t = mdl.binary_var(name='t')

mdl.minimize(v + w + t)
mdl.add_constraint(2 * v + 2 * w + t <= 3, "cons1")
mdl.add_constraint(v + w + t >= 1, "cons2")
mdl.add_constraint(v + w == 1, "cons3")

op = QuadraticProgram()
op.from_docplex(mdl)

admm_params = ADMMParameters(
rho_initial=1001, beta=1000, factor_c=900,
maxiter=100, three_block=False, warm_start=True
)

solver = ADMMOptimizer(params=admm_params)
solution = solver.solve(op)

self.assertIsNotNone(solution)
self.assertIsInstance(solution, ADMMOptimizationResult)
self.assertIsNotNone(solution.x)
np.testing.assert_almost_equal([0., 1., 0.], solution.x, 3)
self.assertIsNotNone(solution.fval)
np.testing.assert_almost_equal(1., solution.fval, 3)
self.assertIsNotNone(solution.state)
self.assertIsInstance(solution.state, ADMMState)

def test_admm_ex6(self):
"""Example 6 as a unit test. Example 6 is reported in:
Gambella, C., & Simonetto, A. (2020).
Expand Down