• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

qiskit-community / qiskit-optimization / 16844546216

06 Aug 2025 01:46PM UTC coverage: 92.15% (-0.04%) from 92.191%
16844546216

push

github

web-flow
Deprecate lp file (#671)

* deprecate read_from_lp_file and write_to_lp_file

* update docs and tests

* reno

* try mode=w

* fix for windows

8 of 8 new or added lines in 2 files covered. (100.0%)

3 existing lines in 2 files now uncovered.

6010 of 6522 relevant lines covered (92.15%)

0.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.81
/qiskit_optimization/translators/docplex_mp.py
1
# This code is part of a Qiskit project.
2
#
3
# (C) Copyright IBM 2021, 2023.
4
#
5
# This code is licensed under the Apache License, Version 2.0. You may
6
# obtain a copy of this license in the LICENSE.txt file in the root directory
7
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8
#
9
# Any modifications or derivative works of this code must retain this
10
# copyright notice, and modified files need to carry a notice indicating
11
# that they have been altered from the originals.
12

13
"""Translator between a docplex.mp model and a quadratic program"""
14

15
from math import isclose
1✔
16
from typing import Any, Dict, Optional, Tuple, cast
1✔
17
from warnings import warn
1✔
18

19
from docplex.mp.basic import Expr
1✔
20
from docplex.mp.constants import ComparisonType
1✔
21
from docplex.mp.constr import (
1✔
22
    IndicatorConstraint,
23
    LinearConstraint,
24
    NotEqualConstraint,
25
    QuadraticConstraint,
26
)
27
from docplex.mp.dvar import Var
1✔
28
from docplex.mp.linear import AbstractLinearExpr
1✔
29
from docplex.mp.model import Model
1✔
30
from docplex.mp.quad import QuadExpr
1✔
31
from docplex.mp.vartype import BinaryVarType, ContinuousVarType, IntegerVarType
1✔
32

33
from qiskit_optimization.exceptions import QiskitOptimizationError
1✔
34
from qiskit_optimization.problems.constraint import Constraint
1✔
35
from qiskit_optimization.problems.quadratic_objective import QuadraticObjective
1✔
36
from qiskit_optimization.problems.quadratic_program import QuadraticProgram
1✔
37
from qiskit_optimization.problems.variable import Variable
1✔
38

39

40
def to_docplex_mp(quadratic_program: QuadraticProgram) -> Model:
1✔
41
    """Returns a docplex.mp model corresponding to a quadratic program.
42

43
    Args:
44
        quadratic_program: The quadratic program to be translated.
45

46
    Returns:
47
        The docplex.mp model corresponding to a quadratic program.
48

49
    Raises:
50
        QiskitOptimizationError: if the model contains non-supported elements (should never happen).
51
    """
52
    # initialize model
53
    mdl = Model(quadratic_program.name)
1✔
54

55
    # add variables
56
    var = {}
1✔
57
    for idx, x in enumerate(quadratic_program.variables):
1✔
58
        if x.vartype == Variable.Type.CONTINUOUS:
1✔
59
            var[idx] = mdl.continuous_var(lb=x.lowerbound, ub=x.upperbound, name=x.name)
1✔
60
        elif x.vartype == Variable.Type.BINARY:
1✔
61
            var[idx] = mdl.binary_var(name=x.name)
1✔
62
        elif x.vartype == Variable.Type.INTEGER:
1✔
63
            var[idx] = mdl.integer_var(lb=x.lowerbound, ub=x.upperbound, name=x.name)
1✔
64
        else:
65
            # should never happen
66
            raise QiskitOptimizationError(f"Internal error: unsupported variable type: {x.vartype}")
×
67

68
    # add objective
69
    objective = (
1✔
70
        quadratic_program.objective.constant
71
        + mdl.sum(
72
            v * var[cast(int, i)] for i, v in quadratic_program.objective.linear.to_dict().items()
73
        )
74
        + mdl.sum(
75
            v * var[cast(int, i)] * var[cast(int, j)]
76
            for (i, j), v in quadratic_program.objective.quadratic.to_dict().items()
77
        )
78
    )
79
    if quadratic_program.objective.sense == QuadraticObjective.Sense.MINIMIZE:
1✔
80
        mdl.minimize(objective)
1✔
81
    else:
UNCOV
82
        mdl.maximize(objective)
×
83

84
    # add linear constraints
85
    for l_constraint in quadratic_program.linear_constraints:
1✔
86
        name = l_constraint.name
1✔
87
        rhs = l_constraint.rhs
1✔
88
        if rhs == 0 and l_constraint.linear.coefficients.nnz == 0:
1✔
89
            continue
×
90
        linear_expr = mdl.sum(
1✔
91
            v * var[cast(int, j)] for j, v in l_constraint.linear.to_dict().items()
92
        )
93
        sense = l_constraint.sense
1✔
94
        if sense == Constraint.Sense.EQ:
1✔
95
            mdl.add_constraint(linear_expr == rhs, ctname=name)
1✔
96
        elif sense == Constraint.Sense.GE:
1✔
97
            mdl.add_constraint(linear_expr >= rhs, ctname=name)
1✔
98
        elif sense == Constraint.Sense.LE:
1✔
99
            mdl.add_constraint(linear_expr <= rhs, ctname=name)
1✔
100
        else:
101
            # should never happen
102
            raise QiskitOptimizationError(f"Internal error: unsupported constraint sense: {sense}")
×
103

104
    # add quadratic constraints
105
    for q_constraint in quadratic_program.quadratic_constraints:
1✔
106
        name = q_constraint.name
1✔
107
        rhs = q_constraint.rhs
1✔
108
        if (
1✔
109
            rhs == 0
110
            and q_constraint.linear.coefficients.nnz == 0
111
            and q_constraint.quadratic.coefficients.nnz == 0
112
        ):
113
            continue
×
114
        quadratic_expr = mdl.sum(
1✔
115
            v * var[cast(int, j)] for j, v in q_constraint.linear.to_dict().items()
116
        ) + mdl.sum(
117
            v * var[cast(int, j)] * var[cast(int, k)]
118
            for (j, k), v in q_constraint.quadratic.to_dict().items()
119
        )
120
        sense = q_constraint.sense
1✔
121
        if sense == Constraint.Sense.EQ:
1✔
122
            mdl.add_constraint(quadratic_expr == rhs, ctname=name)
1✔
123
        elif sense == Constraint.Sense.GE:
1✔
124
            mdl.add_constraint(quadratic_expr >= rhs, ctname=name)
1✔
125
        elif sense == Constraint.Sense.LE:
1✔
126
            mdl.add_constraint(quadratic_expr <= rhs, ctname=name)
1✔
127
        else:
128
            # should never happen
129
            raise QiskitOptimizationError(f"Internal error: unsupported constraint sense: {sense}")
×
130

131
    return mdl
1✔
132

133

134
# from_docplex_mp
135

136

137
class _FromDocplexMp:
1✔
138
    _sense_dict = {ComparisonType.EQ: "==", ComparisonType.LE: "<=", ComparisonType.GE: ">="}
1✔
139

140
    def __init__(self, model: Model):
1✔
141
        """
142
        Args:
143
            model: Docplex model
144
        """
145
        self._model: Model = model
1✔
146
        self._quadratic_program: QuadraticProgram = QuadraticProgram()
1✔
147
        self._var_names: Dict[Var, str] = {}
1✔
148
        self._var_bounds: Dict[str, Tuple[float, float]] = {}
1✔
149

150
    def _variables(self):
1✔
151
        # keep track of names separately, since docplex allows to have None names.
152
        for x in self._model.iter_variables():
1✔
153
            if isinstance(x.vartype, ContinuousVarType):
1✔
154
                x_new = self._quadratic_program.continuous_var(x.lb, x.ub, x.name)
1✔
155
            elif isinstance(x.vartype, BinaryVarType):
1✔
156
                x_new = self._quadratic_program.binary_var(x.name)
1✔
157
            elif isinstance(x.vartype, IntegerVarType):
1✔
158
                x_new = self._quadratic_program.integer_var(x.lb, x.ub, x.name)
1✔
159
            else:
160
                raise QiskitOptimizationError(f"Unsupported variable type: {x.name} {x.vartype}")
1✔
161
            self._var_names[x] = x_new.name
1✔
162
            self._var_bounds[x.name] = (x_new.lowerbound, x_new.upperbound)
1✔
163

164
    def _linear_expr(self, expr: AbstractLinearExpr) -> Dict[str, float]:
1✔
165
        # AbstractLinearExpr is a parent of LinearExpr, ConstantExpr, and ZeroExpr
166
        linear = {}
1✔
167
        for x, coeff in expr.iter_terms():
1✔
168
            linear[self._var_names[x]] = coeff
1✔
169
        return linear
1✔
170

171
    def _quadratic_expr(
1✔
172
        self, expr: QuadExpr
173
    ) -> Tuple[Dict[str, float], Dict[Tuple[str, str], float]]:
174
        linear = self._linear_expr(expr.get_linear_part())
1✔
175
        quad = {}
1✔
176
        for x, y, coeff in expr.iter_quad_triplets():
1✔
177
            i = self._var_names[x]
1✔
178
            j = self._var_names[y]
1✔
179
            quad[i, j] = coeff
1✔
180
        return linear, quad
1✔
181

182
    def quadratic_program(self, indicator_big_m: Optional[float]) -> QuadraticProgram:
1✔
183
        """Generate a quadratic program corresponding to the input Docplex model.
184

185
        Args:
186
            indicator_big_m: The big-M value used for the big-M formulation to convert
187
            indicator constraints into linear constraints.
188
            If ``None``, it is automatically derived from the model.
189

190
        Returns:
191
            a quadratic program corresponding to the input Docplex model.
192

193
        """
194
        self._quadratic_program = QuadraticProgram(self._model.name)
1✔
195

196
        # prepare variables
197
        self._variables()
1✔
198

199
        # objective sense
200
        minimize = self._model.objective_sense.is_minimize()
1✔
201

202
        # make sure objective expression is linear or quadratic and not a variable
203
        if isinstance(self._model.objective_expr, Var):
1✔
204
            self._model.objective_expr = self._model.objective_expr + 0  # Var + 0 -> LinearExpr
×
205

206
        constant = self._model.objective_expr.constant
1✔
207
        if isinstance(self._model.objective_expr, QuadExpr):
1✔
208
            linear, quadratic = self._quadratic_expr(self._model.objective_expr)
1✔
209
        else:
210
            linear = self._linear_expr(self._model.objective_expr.get_linear_part())
1✔
211
            quadratic = {}
1✔
212

213
        # set objective
214
        if minimize:
1✔
215
            self._quadratic_program.minimize(constant, linear, quadratic)
1✔
216
        else:
217
            self._quadratic_program.maximize(constant, linear, quadratic)
1✔
218

219
        # set linear constraints
220
        for constraint in self._model.iter_linear_constraints():
1✔
221
            linear, sense, rhs = self._linear_constraint(constraint)
1✔
222
            if not linear:  # lhs == 0
1✔
223
                warn(f"Trivial constraint: {constraint}", stacklevel=3)
1✔
224
            self._quadratic_program.linear_constraint(linear, sense, rhs, constraint.name)
1✔
225

226
        # set quadratic constraints
227
        for constraint in self._model.iter_quadratic_constraints():
1✔
228
            linear, quadratic, sense, rhs = self._quadratic_constraint(constraint)
1✔
229
            if not linear and not quadratic:  # lhs == 0
1✔
230
                warn(f"Trivial constraint: {constraint}", stacklevel=3)
1✔
231
            self._quadratic_program.quadratic_constraint(
1✔
232
                linear, quadratic, sense, rhs, constraint.name
233
            )
234

235
        # set indicator constraints
236
        for index, constraint in enumerate(self._model.iter_indicator_constraints()):
1✔
237
            linear, _, _ = self._linear_constraint(constraint.linear_constraint)
1✔
238
            if not linear:  # lhs == 0
1✔
239
                warn(f"Trivial constraint: {constraint}", stacklevel=3)
1✔
240
            prefix = constraint.name or f"ind{index}"
1✔
241
            linear_constraints = self._indicator_constraints(constraint, prefix, indicator_big_m)
1✔
242
            for linear, sense, rhs, name in linear_constraints:
1✔
243
                self._quadratic_program.linear_constraint(linear, sense, rhs, name)
1✔
244

245
        return self._quadratic_program
1✔
246

247
    @staticmethod
1✔
248
    def _subtract(dict1: Dict[Any, float], dict2: Dict[Any, float]) -> Dict[Any, float]:
1✔
249
        """Calculate dict1 - dict2"""
250
        ret = dict1.copy()
1✔
251
        for key, val2 in dict2.items():
1✔
252
            if key in dict1:
1✔
253
                val1 = ret[key]
1✔
254
                if isclose(val1, val2):
1✔
255
                    del ret[key]
1✔
256
                else:
257
                    ret[key] -= val2
1✔
258
            else:
259
                ret[key] = -val2
1✔
260
        return ret
1✔
261

262
    def _linear_constraint(
1✔
263
        self, constraint: LinearConstraint
264
    ) -> Tuple[Dict[str, float], str, float]:
265
        left_expr = constraint.get_left_expr()
1✔
266
        right_expr = constraint.get_right_expr()
1✔
267
        # for linear constraints we may get an instance of Var instead of expression,
268
        # e.g. x + y = z
269
        if not isinstance(left_expr, (Expr, Var)):
1✔
270
            raise QiskitOptimizationError(f"Unsupported expression: {left_expr} {type(left_expr)}")
×
271
        if not isinstance(right_expr, (Expr, Var)):
1✔
272
            raise QiskitOptimizationError(
×
273
                f"Unsupported expression: {right_expr} {type(right_expr)}"
274
            )
275
        if constraint.sense not in self._sense_dict:
1✔
276
            raise QiskitOptimizationError(f"Unsupported constraint sense: {constraint}")
×
277

278
        if isinstance(left_expr, Var):
1✔
279
            left_expr = left_expr + 0  # Var + 0 -> LinearExpr
1✔
280
        left_linear = self._linear_expr(left_expr)
1✔
281

282
        if isinstance(right_expr, Var):
1✔
283
            right_expr = right_expr + 0
1✔
284
        right_linear = self._linear_expr(right_expr)
1✔
285

286
        linear = self._subtract(left_linear, right_linear)
1✔
287
        rhs = right_expr.constant - left_expr.constant
1✔
288
        return linear, self._sense_dict[constraint.sense], rhs
1✔
289

290
    def _quadratic_constraint(
1✔
291
        self, constraint: QuadraticConstraint
292
    ) -> Tuple[Dict[str, float], Dict[Tuple[str, str], float], str, float]:
293
        left_expr = constraint.get_left_expr()
1✔
294
        right_expr = constraint.get_right_expr()
1✔
295
        if not isinstance(left_expr, (Expr, Var)):
1✔
296
            raise QiskitOptimizationError(f"Unsupported expression: {left_expr} {type(left_expr)}")
×
297
        if not isinstance(right_expr, (Expr, Var)):
1✔
298
            raise QiskitOptimizationError(
×
299
                f"Unsupported expression: {right_expr} {type(right_expr)}"
300
            )
301
        if constraint.sense not in self._sense_dict:
1✔
302
            raise QiskitOptimizationError(f"Unsupported constraint sense: {constraint}")
×
303

304
        if isinstance(left_expr, Var):
1✔
305
            left_expr = left_expr + 0  # Var + 0 -> LinearExpr
×
306
        if left_expr.is_quad_expr():
1✔
307
            left_lin, left_quad = self._quadratic_expr(left_expr)
1✔
308
        else:
309
            left_lin = self._linear_expr(left_expr)
×
310
            left_quad = {}
×
311

312
        if isinstance(right_expr, Var):
1✔
313
            right_expr = right_expr + 0
×
314
        if right_expr.is_quad_expr():
1✔
315
            right_lin, right_quad = self._quadratic_expr(right_expr)
1✔
316
        else:
317
            right_lin = self._linear_expr(right_expr)
1✔
318
            right_quad = {}
1✔
319

320
        linear = self._subtract(left_lin, right_lin)
1✔
321
        quadratic = self._subtract(left_quad, right_quad)
1✔
322
        rhs = right_expr.constant - left_expr.constant
1✔
323
        return linear, quadratic, self._sense_dict[constraint.sense], rhs
1✔
324

325
    def _linear_bounds(self, linear: Dict[str, float]):
1✔
326
        linear_lb = 0.0
1✔
327
        linear_ub = 0.0
1✔
328
        for var_name, val in linear.items():
1✔
329
            x_lb, x_ub = self._var_bounds[var_name]
1✔
330
            x_lb *= val
1✔
331
            x_ub *= val
1✔
332
            linear_lb += min(x_lb, x_ub)
1✔
333
            linear_ub += max(x_lb, x_ub)
1✔
334
        return linear_lb, linear_ub
1✔
335

336
    def _indicator_constraints(
1✔
337
        self,
338
        constraint: IndicatorConstraint,
339
        name: str,
340
        indicator_big_m: Optional[float] = None,
341
    ):
342
        binary_var = constraint.binary_var
1✔
343
        active_value = constraint.active_value
1✔
344
        linear_constraint = constraint.linear_constraint
1✔
345
        linear, sense, rhs = self._linear_constraint(linear_constraint)
1✔
346
        linear_lb, linear_ub = self._linear_bounds(linear)
1✔
347
        ret = []
1✔
348
        if sense in ["<=", "=="]:
1✔
349
            big_m = max(0.0, linear_ub - rhs) if indicator_big_m is None else indicator_big_m
1✔
350
            if active_value:
1✔
351
                # rhs += big_m * (1 - binary_var)
352
                linear2 = self._subtract(linear, {binary_var.name: -big_m})
1✔
353
                rhs2 = rhs + big_m
1✔
354
            else:
355
                # rhs += big_m * binary_var
356
                linear2 = self._subtract(linear, {binary_var.name: big_m})
1✔
357
                rhs2 = rhs
1✔
358
            name2 = name + "_LE" if sense == "==" else name
1✔
359
            ret.append((linear2, "<=", rhs2, name2))
1✔
360
        if sense in [">=", "=="]:
1✔
361
            big_m = max(0.0, rhs - linear_lb) if indicator_big_m is None else indicator_big_m
1✔
362
            if active_value:
1✔
363
                # rhs += -big_m * (1 - binary_var)
364
                linear2 = self._subtract(linear, {binary_var.name: big_m})
1✔
365
                rhs2 = rhs - big_m
1✔
366
            else:
367
                # rhs += -big_m * binary_var
368
                linear2 = self._subtract(linear, {binary_var.name: -big_m})
1✔
369
                rhs2 = rhs
1✔
370
            name2 = name + "_GE" if sense == "==" else name
1✔
371
            ret.append((linear2, ">=", rhs2, name2))
1✔
372
        if sense not in ["<=", ">=", "=="]:
1✔
373
            raise QiskitOptimizationError(
×
374
                f"Internal error: invalid sense of indicator constraint: {sense}"
375
            )
376
        return ret
1✔
377

378

379
def from_docplex_mp(model: Model, indicator_big_m: Optional[float] = None) -> QuadraticProgram:
1✔
380
    """Translate a docplex.mp model into a quadratic program.
381

382
    Note that this supports the following features of docplex:
383

384
    - linear / quadratic objective function
385
    - linear / quadratic / indicator constraints
386
    - binary / integer / continuous variables
387
    - logical expressions (``logical_not``, ``logical_and``, and ``logical_or``)
388

389
    Args:
390
        model: The docplex.mp model to be loaded.
391
        indicator_big_m: The big-M value used for the big-M formulation to convert
392
            indicator constraints into linear constraints.
393
            If ``None``, it is automatically derived from the model.
394

395
    Returns:
396
        The quadratic program corresponding to the model.
397

398
    Raises:
399
        QiskitOptimizationError: if the model contains unsupported elements.
400
    """
401
    if not isinstance(model, Model):
1✔
402
        raise QiskitOptimizationError(f"The model is not compatible: {model}")
×
403

404
    if model.number_of_user_cut_constraints > 0:
1✔
405
        raise QiskitOptimizationError("User cut constraints are not supported")
1✔
406

407
    if model.number_of_lazy_constraints > 0:
1✔
408
        raise QiskitOptimizationError("Lazy constraints are not supported")
1✔
409

410
    if model.number_of_sos > 0:
1✔
411
        raise QiskitOptimizationError("SOS sets are not supported")
1✔
412

413
    # check constraint type
414
    for constraint in model.iter_constraints():
1✔
415
        # If any constraint is not linear/quadratic/indicator constraints, it raises an error.
416
        if isinstance(constraint, LinearConstraint):
1✔
417
            if isinstance(constraint, NotEqualConstraint):
1✔
418
                # Notice that NotEqualConstraint is a subclass of Docplex's LinearConstraint,
419
                # but it cannot be handled by optimization.
420
                raise QiskitOptimizationError(f"Unsupported constraint: {constraint}")
1✔
421
        elif not isinstance(constraint, (QuadraticConstraint, IndicatorConstraint)):
1✔
422
            raise QiskitOptimizationError(f"Unsupported constraint: {constraint}")
1✔
423

424
    return _FromDocplexMp(model).quadratic_program(indicator_big_m)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc