Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
82 changes: 45 additions & 37 deletions qiskit_optimization/problems/quadratic_program.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from .linear_constraint import LinearConstraint
from .quadratic_constraint import QuadraticConstraint
from .quadratic_objective import QuadraticObjective
from .quadratic_program_element import QuadraticProgramElement
from .variable import Variable, VarType

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -63,14 +64,14 @@ def __init__(self, name: str = "") -> None:
self._name = name
self._status = QuadraticProgram.Status.VALID

self._variables = [] # type: List[Variable]
self._variables_index = {} # type: Dict[str, int]
self._variables: List[Variable] = []
self._variables_index: Dict[str, int] = {}

self._linear_constraints = [] # type: List[LinearConstraint]
self._linear_constraints_index = {} # type: Dict[str, int]
self._linear_constraints: List[LinearConstraint] = []
self._linear_constraints_index: Dict[str, int] = {}

self._quadratic_constraints = [] # type: List[QuadraticConstraint]
self._quadratic_constraints_index = {} # type: Dict[str, int]
self._quadratic_constraints: List[QuadraticConstraint] = []
self._quadratic_constraints_index: Dict[str, int] = {}

self._objective = QuadraticObjective(self)

Expand Down Expand Up @@ -165,28 +166,24 @@ def _add_variables(
key_format: str,
) -> Tuple[List[str], List[Variable]]:
if isinstance(keys, int) and keys < 1:
raise QiskitOptimizationError(
"Cannot create non-positive number of variables: {}".format(keys)
)
raise QiskitOptimizationError(f"Cannot create non-positive number of variables: {keys}")
if name is None:
name = "x"
if "{{}}" in key_format:
raise QiskitOptimizationError(
"Formatter cannot contain nested substitutions: {}".format(key_format)
f"Formatter cannot contain nested substitutions: {key_format}"
)
if key_format.count("{}") > 1:
raise QiskitOptimizationError(
"Formatter cannot contain more than one substitution: {}".format(key_format)
f"Formatter cannot contain more than one substitution: {key_format}"
)

def _find_name(name, key_format, k):
prev = None
while True:
new_name = name + key_format.format(k)
if new_name == prev:
raise QiskitOptimizationError(
"Variable name already exists: {}".format(new_name)
)
raise QiskitOptimizationError(f"Variable name already exists: {new_name}")
if new_name in self._variables_index:
k += 1
prev = new_name
Expand All @@ -204,9 +201,7 @@ def _find_name(name, key_format, k):
else:
indexed_name, k = _find_name(name, key_format, k)
if indexed_name in self._variables_index:
raise QiskitOptimizationError(
"Variable name already exists: {}".format(indexed_name)
)
raise QiskitOptimizationError(f"Variable name already exists: {indexed_name}")
names.append(indexed_name)
self._variables_index[indexed_name] = self.get_num_vars()
variable = Variable(self, indexed_name, lowerbound, upperbound, vartype)
Expand Down Expand Up @@ -622,14 +617,12 @@ def linear_constraint(
"""
if name:
if name in self.linear_constraints_index:
raise QiskitOptimizationError(
"Linear constraint's name already exists: {}".format(name)
)
raise QiskitOptimizationError(f"Linear constraint's name already exists: {name}")
else:
k = self.get_num_linear_constraints()
while "c{}".format(k) in self.linear_constraints_index:
while f"c{k}" in self.linear_constraints_index:
k += 1
name = "c{}".format(k)
name = f"c{k}"
self.linear_constraints_index[name] = len(self.linear_constraints)
if linear is None:
linear = {}
Expand Down Expand Up @@ -715,14 +708,12 @@ def quadratic_constraint(
"""
if name:
if name in self.quadratic_constraints_index:
raise QiskitOptimizationError(
"Quadratic constraint name already exists: {}".format(name)
)
raise QiskitOptimizationError(f"Quadratic constraint name already exists: {name}")
else:
k = self.get_num_quadratic_constraints()
while "q{}".format(k) in self.quadratic_constraints_index:
while f"q{k}" in self.quadratic_constraints_index:
k += 1
name = "q{}".format(k)
name = f"q{k}"
self.quadratic_constraints_index[name] = len(self.quadratic_constraints)
if linear is None:
linear = {}
Expand Down Expand Up @@ -853,6 +844,26 @@ def maximize(
self, constant, linear, quadratic, QuadraticObjective.Sense.MAXIMIZE
)

def _copy_from(self, other: "QuadraticProgram", include_name: bool) -> None:
"""Copy another QuadraticProgram to this updating QuadraticProgramElement

Note: this breaks the consistency of `other`. You cannot use `other` after the copy.

Args:
other: The quadratic program to be copied from.
include_name: Whether this method copies the problem name or not.
"""
for attr, val in vars(other).items():
if attr == "_name" and not include_name:
continue
if isinstance(val, QuadraticProgramElement):
val.quadratic_program = self
if isinstance(val, list):
for elem in val:
if isinstance(elem, QuadraticProgramElement):
elem.quadratic_program = self
setattr(self, attr, val)

@deprecate_method(
"0.2.0", DeprecatedType.FUNCTION, "qiskit_optimization.translators.from_docplex_mp"
)
Expand All @@ -874,8 +885,7 @@ def from_docplex(self, model: Model) -> None:
from ..translators.docplex_mp import from_docplex_mp

other = from_docplex_mp(model)
for attr, val in vars(other).items():
setattr(self, attr, val)
self._copy_from(other, include_name=True)

@deprecate_method(
"0.2.0", DeprecatedType.FUNCTION, "qiskit_optimization.translators.to_docplex_mp"
Expand Down Expand Up @@ -946,8 +956,7 @@ def _parse_problem_name(filename: str) -> str:

model = ModelReader().read(filename, model_name=_parse_problem_name(filename))
other = from_docplex_mp(model)
for attr, val in vars(other).items():
setattr(self, attr, val)
self._copy_from(other, include_name=True)

def write_to_lp_file(self, filename: str) -> None:
"""Writes the quadratic program to an LP file.
Expand Down Expand Up @@ -1048,8 +1057,7 @@ def from_ising(
from ..translators.ising import from_ising

other = from_ising(qubit_op, offset, linear)
for attr, val in vars(other).items():
setattr(self, attr, val)
self._copy_from(other, include_name=False)

def get_feasibility_info(
self, x: Union[List[float], np.ndarray]
Expand All @@ -1068,19 +1076,19 @@ def get_feasibility_info(
# if input `x` is not the same len as the total vars, raise an error
if len(x) != self.get_num_vars():
raise QiskitOptimizationError(
"The size of solution `x`: {}, does not match the number of problem variables: "
"{}".format(len(x), self.get_num_vars())
f"The size of solution `x`: {len(x)}, does not match the number of problem variables: "
f"{self.get_num_vars()}"
)

# check whether the input satisfy the bounds of the problem
violated_variables = [] # type: List[Variable]
violated_variables = []
for i, val in enumerate(x):
variable = self.get_variable(i)
if val < variable.lowerbound or variable.upperbound < val:
violated_variables.append(variable)

# check whether the input satisfy the constraints of the problem
violated_constraints = [] # type: List[Constraint]
violated_constraints = []
for constraint in cast(List[Constraint], self._linear_constraints) + cast(
List[Constraint], self._quadratic_constraints
):
Expand Down
6 changes: 4 additions & 2 deletions test/converters/test_converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,9 +412,10 @@ def test_ising_to_quadraticprogram_linear(self):
op = QUBIT_OP_MAXIMIZE_SAMPLE
offset = OFFSET_MAXIMIZE_SAMPLE

quadratic = QuadraticProgram()
quadratic = QuadraticProgram("test")
quadratic.from_ising(op, offset, linear=True)

self.assertEqual(quadratic.name, "test")
self.assertEqual(quadratic.get_num_vars(), 4)
self.assertEqual(quadratic.get_num_linear_constraints(), 0)
self.assertEqual(quadratic.get_num_quadratic_constraints(), 0)
Expand Down Expand Up @@ -447,9 +448,10 @@ def test_ising_to_quadraticprogram_quadratic(self):
op = QUBIT_OP_MAXIMIZE_SAMPLE
offset = OFFSET_MAXIMIZE_SAMPLE

quadratic = QuadraticProgram()
quadratic = QuadraticProgram("test")
quadratic.from_ising(op, offset, linear=False)

self.assertEqual(quadratic.name, "test")
self.assertEqual(quadratic.get_num_vars(), 4)
self.assertEqual(quadratic.get_num_linear_constraints(), 0)
self.assertEqual(quadratic.get_num_quadratic_constraints(), 0)
Expand Down
28 changes: 28 additions & 0 deletions test/problems/test_quadratic_program.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

from docplex.mp.model import DOcplexException, Model

from qiskit.opflow import PauliSumOp
from qiskit_optimization import INFINITY, QiskitOptimizationError, QuadraticProgram
from qiskit_optimization.problems import Constraint, QuadraticObjective, Variable, VarType

Expand Down Expand Up @@ -1117,6 +1118,33 @@ def test_feasibility(self):
self.assertEqual("c3", constraints[1].name)
self.assertEqual("c5", constraints[2].name)

@requires_extra_library
def test_quadratic_program_element_when_loaded_from_source(self):
"""Test QuadraticProgramElement when QuadraticProgram is loaded from an external source"""
with self.subTest("from_ising"):
q_p = QuadraticProgram()
q_p.from_ising(PauliSumOp.from_list([("IZ", 1), ("ZZ", 2)]))
self.assertEqual(id(q_p.objective.quadratic_program), id(q_p))
for elem in q_p.variables:
self.assertEqual(id(elem.quadratic_program), id(q_p))
for elem in q_p.linear_constraints:
self.assertEqual(id(elem.quadratic_program), id(q_p))
for elem in q_p.quadratic_constraints:
self.assertEqual(id(elem.quadratic_program), id(q_p))
try:
lp_file = self.get_resource_path("test_quadratic_program.lp", "problems/resources")
q_p = QuadraticProgram()
q_p.read_from_lp_file(lp_file)
self.assertEqual(id(q_p.objective.quadratic_program), id(q_p))
for elem in q_p.variables:
self.assertEqual(id(elem.quadratic_program), id(q_p))
for elem in q_p.linear_constraints:
self.assertEqual(id(elem.quadratic_program), id(q_p))
for elem in q_p.quadratic_constraints:
self.assertEqual(id(elem.quadratic_program), id(q_p))
except RuntimeError as ex:
self.fail(str(ex))


if __name__ == "__main__":
unittest.main()