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

For the Enhancement of converters of QuadraticProgram #1061

Merged
merged 43 commits into from
Aug 3, 2020
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
2c1e762
refactored converter's constructor
a-matsuo Jun 19, 2020
eaa1cf1
Merge branch 'master' into converter_constructor
woodsp-ibm Jun 23, 2020
7dd3692
Merge branch 'master' into converter_constructor
manoelmarques Jun 24, 2020
73af26f
added baseconverter class
a-matsuo Jul 6, 2020
281ebe9
Merge branch 'master' into converter_constructor
manoelmarques Jul 6, 2020
fabdc3a
added is_compatible_with_integer_slack
a-matsuo Jul 7, 2020
8c9e474
added InequalityToEquality converter to QuadraticProgramToQubo
a-matsuo Jul 7, 2020
b657d41
Merge branch 'converter_constructor' of github.com:a-matsuo/qiskit-aq…
a-matsuo Jul 7, 2020
5669d81
refactor InequalityToEquality converter
a-matsuo Jul 7, 2020
6efbdcf
refactored encode and decode name
a-matsuo Jul 7, 2020
be17abe
Merge branch 'master' into converter_constructor
manoelmarques Jul 7, 2020
5cb01a0
renamed encode/decode to convert/interpret
a-matsuo Jul 8, 2020
64111f2
renamed encode/decode to convert/interpret
a-matsuo Jul 8, 2020
07c8e4e
fixed lint and changed to deep copy results
a-matsuo Jul 8, 2020
abb351d
added getter/setter for linear
a-matsuo Jul 8, 2020
bd0fec0
Merge branch 'master' into converter_constructor
Cryoris Jul 9, 2020
dbf881b
reflect EqualityToPenalty converter changes
a-matsuo Jul 10, 2020
8fb0c25
fixed
a-matsuo Jul 10, 2020
fb66959
fixed lint and mypy errors
a-matsuo Jul 10, 2020
cc2beb2
fixed a style error
a-matsuo Jul 10, 2020
5b4077c
moved `name` to convert method
a-matsuo Jul 15, 2020
f73fbd8
fixed IsingToQuadraticProgram
a-matsuo Jul 15, 2020
2ff04ea
added reno file
a-matsuo Jul 15, 2020
26da9c9
fixed
a-matsuo Jul 15, 2020
d512032
removed `name` arguments from converters
a-matsuo Jul 15, 2020
ae5da14
fixed issues
a-matsuo Jul 16, 2020
d034110
fixed name
a-matsuo Jul 16, 2020
7673009
Merge branch 'master' into converter_constructor
woodsp-ibm Jul 16, 2020
e8ed1f0
Merge branch 'master' into converter_constructor
woodsp-ibm Jul 17, 2020
c3ce427
Merge branch 'master' into converter_constructor
woodsp-ibm Jul 17, 2020
bb22c9f
added to_ising and from_ising
a-matsuo Jul 22, 2020
d45a18d
Merge branch 'converter_constructor' of github.com:a-matsuo/qiskit-aq…
a-matsuo Jul 22, 2020
5b56f0c
updated release notes
a-matsuo Jul 22, 2020
0f14ee1
Merge branch 'master' into converter_constructor
manoelmarques Jul 23, 2020
44149e4
fixed the release note
a-matsuo Jul 29, 2020
0827f25
Merge remote-tracking branch 'upstream/master' into converter_constru…
a-matsuo Jul 30, 2020
6c9ada1
Merge remote-tracking branch 'upstream/master' into converter_constru…
a-matsuo Aug 3, 2020
89f4695
added deprecated messages and fixed
a-matsuo Aug 3, 2020
c543a7d
fixed lint
a-matsuo Aug 3, 2020
41f7315
fixed lint
a-matsuo Aug 3, 2020
06fcdfb
moved converters to constructors of algorithms
a-matsuo Aug 3, 2020
1317ae3
Merge remote-tracking branch 'upstream/master' into converter_constru…
a-matsuo Aug 3, 2020
b3cf5db
fixed deprecated msg and added conv to results
a-matsuo Aug 3, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions qiskit/optimization/algorithms/admm_optimizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult:
# map integer variables to binary variables
from ..converters.integer_to_binary import IntegerToBinary
int2bin = IntegerToBinary()
problem = int2bin.encode(problem)
problem = int2bin.convert(problem)

# we deal with minimization in the optimizer, so turn the problem to minimization
problem, sense = self._turn_to_minimization(problem)
Expand Down Expand Up @@ -362,7 +362,7 @@ def solve(self, problem: QuadraticProgram) -> ADMMOptimizationResult:
result = ADMMOptimizationResult(solution, objective_value, self._state)

# convert back integer to binary
result = cast(ADMMOptimizationResult, int2bin.decode(result))
result = cast(ADMMOptimizationResult, int2bin.interpret(result))
# debug
self._log.debug("solution=%s, objective=%s at iteration=%s",
solution, objective_value, iteration)
Expand Down
36 changes: 36 additions & 0 deletions qiskit/optimization/converters/base_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-

# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""An abstract class for optimization algorithms in Qiskit's optimization module."""

from abc import ABC, abstractmethod

from ..algorithms.optimization_algorithm import OptimizationResult
from ..problems.quadratic_program import QuadraticProgram


class BaseConverter(ABC):
"""
An abstract class for converters of quadratic programs in Qiskit's optimization module.
"""

@abstractmethod
def convert(self, problem: QuadraticProgram) -> QuadraticProgram:
""" convert an QuadratciProgram into an QuadraticProgram with a specific form"""
raise NotImplementedError

@abstractmethod
def interpret(self, result: OptimizationResult) -> OptimizationResult:
""" interpret an OptimizationResult based on the original QuadraticProgram"""
raise NotImplementedError
80 changes: 54 additions & 26 deletions qiskit/optimization/converters/inequality_to_equality.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from typing import List, Optional, Dict, Tuple
import logging

from .base_converter import BaseConverter
from ..algorithms.optimization_algorithm import OptimizationResult
from ..problems.quadratic_program import QuadraticProgram
from ..problems.quadratic_objective import QuadraticObjective
Expand All @@ -28,7 +29,7 @@
logger = logging.getLogger(__name__)


class InequalityToEquality:
class InequalityToEquality(BaseConverter):
"""Convert inequality constraints into equality constraints by introducing slack variables.

Examples:
Expand All @@ -42,26 +43,30 @@ class InequalityToEquality:

_delimiter = '@' # users are supposed not to use this character in variable names

def __init__(self) -> None:
def __init__(self, name: Optional[str] = None, mode: str = 'auto') -> None:
woodsp-ibm marked this conversation as resolved.
Show resolved Hide resolved
"""
Args:
name: The name of the converted problem. If not provided, the name of the input
problem is used.
mode: To chose the type of slack variables. There are 3 options for mode.

- 'integer': All slack variables will be integer variables.
- 'continuous': All slack variables will be continuous variables
- 'auto': Try to use integer variables but if it's not possible,
use continuous variables
"""
self._src = None # type: Optional[QuadraticProgram]
self._dst = None # type: Optional[QuadraticProgram]
self._dst_name = name
self._mode = mode
self._conv = {} # type: Dict[str, List[Tuple[str, int]]]
# e.g., self._conv = {'c1': [c1@slack_var]}

def encode(
self, op: QuadraticProgram, name: Optional[str] = None, mode: str = 'auto'
) -> QuadraticProgram:
def convert(self, problem: QuadraticProgram) -> QuadraticProgram:
"""Convert a problem with inequality constraints into one with only equality constraints.

Args:
op: The problem to be solved, that may contain inequality constraints.
name: The name of the converted problem.
mode: To chose the type of slack variables. There are 3 options for mode.

- 'integer': All slack variables will be integer variables.
- 'continuous': All slack variables will be continuous variables
- 'auto': Try to use integer variables but if it's not possible,
use continuous variables
problem: The problem to be solved, that may contain inequality constraints.

Returns:
The converted problem, that contain only equality constraints.
Expand All @@ -71,12 +76,17 @@ def encode(
QiskitOptimizationError: If an unsupported mode is selected.
QiskitOptimizationError: If an unsupported sense is specified.
"""
self._src = copy.deepcopy(op)
self._src = copy.deepcopy(problem)
self._dst = QuadraticProgram()
if name:
self._dst.name = name
else:

# set a problem name
if self._dst_name is None:
self._dst.name = self._src.name
else:
self._dst.name = self._dst_name

# set a converting mode
mode = self._mode

# Copy variables
for x in self._src.variables:
Expand Down Expand Up @@ -234,12 +244,11 @@ def _add_auto_slack_var_linear_constraint(self, linear, sense, rhs, name):
self._add_integer_slack_var_linear_constraint(linear, sense, rhs, name)

def _add_integer_slack_var_quadratic_constraint(self, linear, quadratic, sense, rhs, name):
# If a coefficient that is not integer exist, raise an error
# If a coefficient that is not integer exist, raise error
if any(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar float check appears often in the class. So, I suggest to introduce a static method.

@staticmethod
    def _contains_any_float_value(values: List[Union[int, float]]) -> bool:
        return any(isinstance(val, float) and not val.is_integer() for val in values)

isinstance(coef, float) and not coef.is_integer() for coef in quadratic.values()
) or any(isinstance(coef, float) and not coef.is_integer() for coef in linear.values()):
raise QiskitOptimizationError('Can not use a slack variable for ' + name)

# If rhs is float number, round up/down to the nearest integer.
new_rhs = rhs
if sense == Constraint.Sense.LE:
Expand Down Expand Up @@ -268,12 +277,6 @@ def _add_integer_slack_var_quadratic_constraint(self, linear, quadratic, sense,
self._dst.quadratic_constraint(new_linear, quadratic, "==", new_rhs, name)

def _add_continuous_slack_var_quadratic_constraint(self, linear, quadratic, sense, rhs, name):
# If a coefficient that is not integer exist, raise error
if any(
isinstance(coef, float) and not coef.is_integer() for coef in quadratic.values()
) or any(isinstance(coef, float) and not coef.is_integer() for coef in linear.values()):
raise QiskitOptimizationError('Can not use a slack variable for ' + name)

# Add a new continuous variable.
slack_name = name + self._delimiter + 'continuous_slack'
self._conv[name] = slack_name
Expand Down Expand Up @@ -338,7 +341,7 @@ def _calc_quadratic_bounds(self, linear, quadratic):
)
return lhs_lb, lhs_ub

def decode(self, result: OptimizationResult) -> OptimizationResult:
def interpret(self, result: OptimizationResult) -> OptimizationResult:
"""Convert a result of a converted problem into that of the original problem.

Args:
Expand All @@ -363,3 +366,28 @@ def _decode_var(self, names, vals) -> List[int]:
for x in self._src.variables:
new_vals.append(sol[x.name])
return new_vals

def is_compatible_with_integer_slack(self, prog: QuadraticProgram) -> bool:
"""Checks whether the integer slack mdoe can be used for a given problem.
woodsp-ibm marked this conversation as resolved.
Show resolved Hide resolved
Args:
prog: The optimization problem to check compatibility.

Returns:
Returns True if the problem is compatible, False otherwise.
"""
compatible=True

# If a coefficient that is not integer exist, set compatible False
for l_constraint in prog.linear_constraints:
linear = l_constraint.linear.to_dict()
if any(isinstance(coef, float) and not coef.is_integer() for coef in linear.values()):
compatible = False
for q_constraint in prog.quadratic_constraints:
linear = q_constraint.linear.to_dict()
quadratic = q_constraint.quadratic.to_dict()
if any(
isinstance(coef, float) and not coef.is_integer() for coef in quadratic.values()
) or any(isinstance(coef, float) and not coef.is_integer() for coef in linear.values()):
compatible = False

return compatible
24 changes: 15 additions & 9 deletions qiskit/optimization/converters/integer_to_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import numpy as np

from .base_converter import BaseConverter
from ..algorithms.optimization_algorithm import OptimizationResult
from ..exceptions import QiskitOptimizationError
from ..problems.quadratic_objective import QuadraticObjective
Expand All @@ -29,7 +30,7 @@
logger = logging.getLogger(__name__)


class IntegerToBinary:
class IntegerToBinary(BaseConverter):
"""Convert a :class:`~qiskit.optimization.problems.QuadraticProgram` into new one by encoding
integer with binary variables.

Expand All @@ -50,19 +51,24 @@ class IntegerToBinary:

_delimiter = '@' # users are supposed not to use this character in variable names

def __init__(self) -> None:
def __init__(self, name: Optional[str] = None) -> None:
"""
Args:
name: The name of the converted problem. If not provided, the name of the input
problem is used.
"""
self._src = None # type: Optional[QuadraticProgram]
self._dst = None # type: Optional[QuadraticProgram]
self._dst_name = name
self._conv = {} # type: Dict[Variable, List[Tuple[str, int]]]

# e.g., self._conv = {x: [('x@1', 1), ('x@2', 2)]}

def encode(self, op: QuadraticProgram, name: Optional[str] = None) -> QuadraticProgram:
stefan-woerner marked this conversation as resolved.
Show resolved Hide resolved
def convert(self, op: QuadraticProgram) -> QuadraticProgram:
"""Convert an integer problem into a new problem with binary variables.

Args:
op: The problem to be solved, that may contain integer variables.
name: The name of the converted problem. If not provided, the name of the input
problem is used.

Returns:
The converted problem, that contains no integer variables.
Expand Down Expand Up @@ -102,10 +108,10 @@ def encode(self, op: QuadraticProgram, name: Optional[str] = None) -> QuadraticP
self._dst = copy.deepcopy(op)

# adjust name of resulting problem if necessary
if name:
self._dst.name = name
else:
if self._dst.name is None:
self._dst.name = self._src.name
else:
self._dst.name = self._dst_name

return self._dst

Expand Down Expand Up @@ -206,7 +212,7 @@ def _substitute_int_var(self):
self._dst.quadratic_constraint(linear, quadratic, constraint.sense,
constraint.rhs - constant, constraint.name)

def decode(self, result: OptimizationResult) -> OptimizationResult:
def interpret(self, result: OptimizationResult) -> OptimizationResult:
"""Convert the encoded problem (binary variables) back to the original (integer variables).

Args:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def __init__(self, linear: bool = False) -> None:
self._qp = None # type: Optional[QuadraticProgram]
self._linear = linear

def encode(self, qubit_op: Union[OperatorBase, WeightedPauliOperator], offset: float = 0.0
def convert(self, qubit_op: Union[OperatorBase, WeightedPauliOperator], offset: float = 0.0
) -> QuadraticProgram:
"""Convert a qubit operator and a shift value into a quadratic program

Expand Down
19 changes: 14 additions & 5 deletions qiskit/optimization/converters/quadratic_program_to_qubo.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
from ..algorithms.optimization_algorithm import OptimizationResult
from ..problems.quadratic_program import QuadraticProgram
from ..problems.constraint import Constraint
from ..converters.linear_equality_to_penalty import LinearEqualityToPenalty
from ..exceptions import QiskitOptimizationError
from ..converters.inequality_to_equality import InequalityToEquality
from ..converters.linear_equality_to_penalty import LinearEqualityToPenalty


class QuadraticProgramToQubo:
Expand All @@ -35,13 +36,14 @@ class QuadraticProgramToQubo:
>>> problem2 = conv.encode(problem)
"""

def __init__(self, penalty: Optional[float] = None) -> None:
def __init__(self, penalty: Optional[float] = None, mode: str = 'auto') -> None:
"""
Args:
penalty: Penalty factor to scale equality constraints that are added to objective.
"""
from ..converters.integer_to_binary import IntegerToBinary
self._int_to_bin = IntegerToBinary()
self._ineq_to_eq = InequalityToEquality(mode='int')
self._penalize_lin_eq_constraints = LinearEqualityToPenalty()
self._penalty = penalty

Expand All @@ -63,8 +65,11 @@ def encode(self, problem: QuadraticProgram) -> QuadraticProgram:
if len(msg) > 0:
raise QiskitOptimizationError('Incompatible problem: {}'.format(msg))

# convert inequality constraints into equality constraints by adding slack variables
problem_ = self._ineq_to_eq.convert(problem)

# map integer variables to binary variables
problem_ = self._int_to_bin.encode(problem)
problem_ = self._int_to_bin.convert(problem_)

# penalize linear equality constraints with only binary variables
if self._penalty is None:
Expand All @@ -86,7 +91,7 @@ def decode(self, result: OptimizationResult) -> OptimizationResult:
Returns:
The result of the original problem.
"""
return self._int_to_bin.decode(result)
return self._int_to_bin.interpret(result)

@staticmethod
def get_compatibility_msg(problem: QuadraticProgram) -> str:
Expand All @@ -104,7 +109,7 @@ def get_compatibility_msg(problem: QuadraticProgram) -> str:

# initialize message
msg = ''

print(problem)
woodsp-ibm marked this conversation as resolved.
Show resolved Hide resolved
# check whether there are incompatible variable types
if problem.get_num_continuous_vars() > 0:
msg += 'Continuous variables are not supported! '
Expand All @@ -115,6 +120,10 @@ def get_compatibility_msg(problem: QuadraticProgram) -> str:
msg += 'Only linear equality constraints are supported.'
woodsp-ibm marked this conversation as resolved.
Show resolved Hide resolved
if len(problem.quadratic_constraints) > 0:
msg += 'Quadratic constraints are not supported. '
# check whether there are float coefficients in constraints
ineq_to_eq = InequalityToEquality()
if not ineq_to_eq.is_compatible_with_integer_slack(problem):
msg += 'Float coefficients are in constraints.'

# if an error occurred, return error message, otherwise, return None
return msg
Expand Down
Loading