Skip to content

Commit

Permalink
Added converters argument to some optimization algorithms (qiskit-c…
Browse files Browse the repository at this point in the history
…ommunity/qiskit-aqua#1260)

* added converter argument to opt algorithms

* now penalty parameter is passed properly

* added docstrings for penalty

Co-authored-by: Manoel Marques <Manoel.Marques@ibm.com>
Co-authored-by: Cryoris <jules.gacon@googlemail.com>
Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
  • Loading branch information
4 people authored Oct 5, 2020
1 parent 85670aa commit 6a89391
Show file tree
Hide file tree
Showing 12 changed files with 331 additions and 107 deletions.
24 changes: 18 additions & 6 deletions qiskit/optimization/algorithms/grover_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from qiskit.circuit.library import QuadraticForm
from .optimization_algorithm import (OptimizationResultStatus, OptimizationAlgorithm,
OptimizationResult)
from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo
from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo, QuadraticProgramConverter
from ..problems import Variable
from ..problems.quadratic_program import QuadraticProgram

Expand All @@ -37,23 +37,35 @@ class GroverOptimizer(OptimizationAlgorithm):
"""Uses Grover Adaptive Search (GAS) to find the minimum of a QUBO function."""

def __init__(self, num_value_qubits: int, num_iterations: int = 3,
quantum_instance: Optional[Union[BaseBackend, QuantumInstance]] = None) -> None:
quantum_instance: Optional[Union[BaseBackend, QuantumInstance]] = None,
converters: Optional[Union[QuadraticProgramConverter,
List[QuadraticProgramConverter]]] = None,
penalty: Optional[float] = None) -> None:
"""
Args:
num_value_qubits: The number of value qubits.
num_iterations: The number of iterations the algorithm will search with
no improvement.
quantum_instance: Instance of selected backend, defaults to Aer's statevector simulator.
converters: The converters to use for converting a problem into a different form.
By default, when None is specified, an internally created instance of
:class:`~qiskit.optimization.converters.QuadraticProgramToQubo` will be used.
penalty: The penalty factor used in the default
:class:`~qiskit.optimization.converters.QuadraticProgramToQubo` converter
Raises:
TypeError: When there one of converters is an invalid type.
"""
self._num_value_qubits = num_value_qubits
self._num_key_qubits = None
self._n_iterations = num_iterations
self._quantum_instance = None
self._qubo_converter = QuadraticProgramToQubo()

if quantum_instance is not None:
self.quantum_instance = quantum_instance

self._converters = self._prepare_converters(converters, penalty)

@property
def quantum_instance(self) -> QuantumInstance:
"""The quantum instance to run the circuits.
Expand Down Expand Up @@ -142,7 +154,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult:
self._verify_compatibility(problem)

# convert problem to QUBO
problem_ = self._qubo_converter.convert(problem)
problem_ = self._convert(problem, self._converters)
problem_init = deepcopy(problem_)

# convert to minimization problem
Expand All @@ -154,7 +166,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult:
problem_.objective.linear[i] = -val
for (i, j), val in problem_.objective.quadratic.to_dict().items():
problem_.objective.quadratic[i, j] = -val
self._num_key_qubits = len(problem_.objective.linear.to_array())
self._num_key_qubits = len(problem_.objective.linear.to_array()) # type: ignore

# Variables for tracking the optimum.
optimum_found = False
Expand Down Expand Up @@ -259,7 +271,7 @@ def solve(self, problem: QuadraticProgram) -> OptimizationResult:
status=OptimizationResultStatus.SUCCESS)

# cast binaries back to integers
result = self._qubo_converter.interpret(result)
result = self._interpret(result, self._converters)

return GroverOptimizationResult(x=result.x, fval=result.fval, variables=result.variables,
operation_counts=operation_count, n_input_qubits=n_key,
Expand Down
21 changes: 15 additions & 6 deletions qiskit/optimization/algorithms/minimum_eigen_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from .optimization_algorithm import (OptimizationResultStatus, OptimizationAlgorithm,
OptimizationResult)
from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo
from ..converters.quadratic_program_to_qubo import QuadraticProgramToQubo, QuadraticProgramConverter
from ..problems.quadratic_program import QuadraticProgram, Variable


Expand Down Expand Up @@ -101,8 +101,9 @@ class MinimumEigenOptimizer(OptimizationAlgorithm):
result = optimizer.solve(problem)
"""

def __init__(self, min_eigen_solver: MinimumEigensolver, penalty: Optional[float] = None
) -> None:
def __init__(self, min_eigen_solver: MinimumEigensolver, penalty: Optional[float] = None,
converters: Optional[Union[QuadraticProgramConverter,
List[QuadraticProgramConverter]]] = None) -> None:
"""
This initializer takes the minimum eigen solver to be used to approximate the ground state
of the resulting Hamiltonian as well as a optional penalty factor to scale penalty terms
Expand All @@ -112,10 +113,17 @@ def __init__(self, min_eigen_solver: MinimumEigensolver, penalty: Optional[float
Args:
min_eigen_solver: The eigen solver to find the ground state of the Hamiltonian.
penalty: The penalty factor to be used, or ``None`` for applying a default logic.
converters: The converters to use for converting a problem into a different form.
By default, when None is specified, an internally created instance of
:class:`~qiskit.optimization.converters.QuadraticProgramToQubo` will be used.
Raises:
TypeError: When there one of converters is an invalid type.
"""
self._min_eigen_solver = min_eigen_solver
self._penalty = penalty
self._qubo_converter = QuadraticProgramToQubo()

self._converters = self._prepare_converters(converters, penalty)

def get_compatibility_msg(self, problem: QuadraticProgram) -> str:
"""Checks whether a given problem can be solved with this optimizer.
Expand Down Expand Up @@ -158,7 +166,7 @@ def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizationResult:
self._verify_compatibility(problem)

# convert problem to QUBO
problem_ = self._qubo_converter.convert(problem)
problem_ = self._convert(problem, self._converters)

# construct operator and offset
operator, offset = problem_.to_ising()
Expand Down Expand Up @@ -191,7 +199,8 @@ def solve(self, problem: QuadraticProgram) -> MinimumEigenOptimizationResult:
# translate result back to integers
result = OptimizationResult(x=x, fval=fval, variables=problem_.variables,
status=OptimizationResultStatus.SUCCESS)
result = self._qubo_converter.interpret(result)

result = self._interpret(result, self._converters)

return MinimumEigenOptimizationResult(x=result.x, fval=result.fval,
variables=result.variables,
Expand Down
234 changes: 155 additions & 79 deletions qiskit/optimization/algorithms/optimization_algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

from .. import QiskitOptimizationError
from ..problems.quadratic_program import QuadraticProgram, Variable
from ..converters.quadratic_program_to_qubo import (QuadraticProgramToQubo,
QuadraticProgramConverter)


class OptimizationResultStatus(Enum):
Expand All @@ -35,85 +37,6 @@ class OptimizationResultStatus(Enum):
"""the optimization algorithm obtained an infeasible solution."""


class OptimizationAlgorithm(ABC):
"""An abstract class for optimization algorithms in Qiskit's optimization module."""

@abstractmethod
def get_compatibility_msg(self, problem: QuadraticProgram) -> str:
"""Checks whether a given problem can be solved with the optimizer implementing this method.
Args:
problem: The optimization problem to check compatibility.
Returns:
Returns the incompatibility message. If the message is empty no issues were found.
"""

def is_compatible(self, problem: QuadraticProgram) -> bool:
"""Checks whether a given problem can be solved with the optimizer implementing this method.
Args:
problem: The optimization problem to check compatibility.
Returns:
Returns True if the problem is compatible, False otherwise.
"""
return len(self.get_compatibility_msg(problem)) == 0

@abstractmethod
def solve(self, problem: QuadraticProgram) -> 'OptimizationResult':
"""Tries to solves the given problem using the optimizer.
Runs the optimizer to try to solve the optimization problem.
Args:
problem: The problem to be solved.
Returns:
The result of the optimizer applied to the problem.
Raises:
QiskitOptimizationError: If the problem is incompatible with the optimizer.
"""
raise NotImplementedError

def _verify_compatibility(self, problem: QuadraticProgram) -> None:
"""Verifies that the problem is suitable for this optimizer. If the problem is not
compatible then an exception is raised. This method is for convenience for concrete
optimizers and is not intended to be used by end user.
Args:
problem: Problem to verify.
Returns:
None
Raises:
QiskitOptimizationError: If the problem is incompatible with the optimizer.
"""
# check compatibility and raise exception if incompatible
msg = self.get_compatibility_msg(problem)
if msg:
raise QiskitOptimizationError('Incompatible problem: {}'.format(msg))

def _get_feasibility_status(self, problem: QuadraticProgram,
x: Union[List[float], np.ndarray]) -> OptimizationResultStatus:
"""Returns whether the input result is feasible or not for the given problem.
Args:
problem: Problem to verify.
x: the input result list.
Returns:
The status of the result.
"""
is_feasible = problem.is_feasible(x)

return OptimizationResultStatus.SUCCESS if is_feasible \
else OptimizationResultStatus.INFEASIBLE


class OptimizationResult:
"""A base class for optimization results.
Expand Down Expand Up @@ -279,3 +202,156 @@ def variable_names(self) -> List[str]:
The list of variable names of the optimization problem.
"""
return self._variable_names


class OptimizationAlgorithm(ABC):
"""An abstract class for optimization algorithms in Qiskit's optimization module."""

@abstractmethod
def get_compatibility_msg(self, problem: QuadraticProgram) -> str:
"""Checks whether a given problem can be solved with the optimizer implementing this method.
Args:
problem: The optimization problem to check compatibility.
Returns:
Returns the incompatibility message. If the message is empty no issues were found.
"""

def is_compatible(self, problem: QuadraticProgram) -> bool:
"""Checks whether a given problem can be solved with the optimizer implementing this method.
Args:
problem: The optimization problem to check compatibility.
Returns:
Returns True if the problem is compatible, False otherwise.
"""
return len(self.get_compatibility_msg(problem)) == 0

@abstractmethod
def solve(self, problem: QuadraticProgram) -> 'OptimizationResult':
"""Tries to solves the given problem using the optimizer.
Runs the optimizer to try to solve the optimization problem.
Args:
problem: The problem to be solved.
Returns:
The result of the optimizer applied to the problem.
Raises:
QiskitOptimizationError: If the problem is incompatible with the optimizer.
"""
raise NotImplementedError

def _verify_compatibility(self, problem: QuadraticProgram) -> None:
"""Verifies that the problem is suitable for this optimizer. If the problem is not
compatible then an exception is raised. This method is for convenience for concrete
optimizers and is not intended to be used by end user.
Args:
problem: Problem to verify.
Returns:
None
Raises:
QiskitOptimizationError: If the problem is incompatible with the optimizer.
"""
# check compatibility and raise exception if incompatible
msg = self.get_compatibility_msg(problem)
if msg:
raise QiskitOptimizationError('Incompatible problem: {}'.format(msg))

def _get_feasibility_status(self, problem: QuadraticProgram,
x: Union[List[float], np.ndarray]) -> OptimizationResultStatus:
"""Returns whether the input result is feasible or not for the given problem.
Args:
problem: Problem to verify.
x: the input result list.
Returns:
The status of the result.
"""
is_feasible = problem.is_feasible(x)

return OptimizationResultStatus.SUCCESS if is_feasible \
else OptimizationResultStatus.INFEASIBLE

def _prepare_converters(self, converters: Optional[Union[QuadraticProgramConverter,
List[QuadraticProgramConverter]]],
penalty: Optional[float] = None) -> List[QuadraticProgramConverter]:
"""Prepare a list of converters from the input.
Args:
converters: The converters to use for converting a problem into a different form.
By default, when None is specified, an internally created instance of
:class:`~qiskit.optimization.converters.QuadraticProgramToQubo` will be used.
penalty: The penalty factor used in the default
:class:`~qiskit.optimization.converters.QuadraticProgramToQubo` converter
Returns:
The list of converters.
Raises:
TypeError: When the converters include those that are not
:class:`~qiskit.optimization.converters.QuadraticProgramConverter type.
"""
converters_ = [] # type: List[QuadraticProgramConverter]
if converters is None:
converters_ = [QuadraticProgramToQubo(penalty=penalty)]
elif isinstance(converters, QuadraticProgramConverter):
converters_ = [converters]
elif isinstance(converters, list) and \
all(isinstance(converter, QuadraticProgramConverter) for converter in converters):
converters_ = converters
else:
raise TypeError('`converters` must all be of the QuadraticProgramConverter type')

return converters_

def _convert(self, problem: QuadraticProgram,
converters: Union[QuadraticProgramConverter,
List[QuadraticProgramConverter]]) -> QuadraticProgram:
"""Convert the problem with the converters
Args:
problem: The problem to be solved
converters: The converters to use for converting a problem into a different form.
Returns:
The problem converted by the converters.
"""
problem_ = problem

if not isinstance(converters, list):
converters = [converters]

for converter in converters:
problem_ = converter.convert(problem_)

return problem_

def _interpret(self, result: OptimizationResult,
converters: Union[QuadraticProgramConverter,
List[QuadraticProgramConverter]]) -> OptimizationResult:
"""Convert back the result of the converted problem to the result of the original problem.
Args:
result: The result of the converted problem.
converters: The converters to use for converting back the result of the problem
to the result of the original problem.
Returns:
The result of the original problem.
"""
if not isinstance(converters, list):
converters = [converters]

for converter in converters[::-1]:
result = converter.interpret(result)
return result
Loading

0 comments on commit 6a89391

Please sign in to comment.